I am using ASP.NET Core 2 with Razor Pages and I am trying to have two forms with separate properties (BindProperty) on one page.
@page
@model mfa.Web.Pages.TwoFormsModel
@{
Layout = null;
}
<form method="post">
<input asp-for="ProductName" />
<span asp-validation-for="ProductName" class="text-danger"></span>
<button type="submit" asp-page-handler="Product">Save product</button>
</form>
<form method="post">
<input asp-for="MakerName" />
<span asp-validation-for="MakerName" class="text-danger"></span>
<button type="submit" asp-page-handler="Maker">Save maker</button>
</form>
And the corresponding PageModel:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace mfa.Web.Pages
{
public class TwoFormsModel : PageModel
{
[BindProperty]
[Required]
public string ProductName { get; set; }
[BindProperty]
[Required]
public string MakerName { get; set; }
public async Task<IActionResult> OnPostProductAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
return Page();
}
public async Task<IActionResult> OnPostMakerAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
return Page();
}
}
}
Hitting any of the two submit buttons brings me in the corresponding post handler. Both "ProdutName" and "MakerName" are populated corectly with whatever I typed in the corresponding input fields. So far, so good.
But: ModelState.IsValid() always returns true - no matter if the value of the corresponding property has a value or not. ModelState.IsValid() is true even when both properties are null.
Also: OnPostProductAsync() should only validate "ProductName" and accordingly OnPostMakerAsync() should only validate "MakerName".
Can this be done at all? Or am I asking too much from Razor Pages? There are plenty of blogs and tutorials that show you how to have two forms on one page ... but they are all using the same model. I need different models!
My solution isn't very elegant, but it doesn't require you to manually do the validations. You can keep the [Required]
annotations.
Your PageModel
will look like this -
private void ClearFieldErrors(Func<string, bool> predicate)
{
foreach (var field in ModelState)
{
if (field.Value.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{
if (predicate(field.Key))
{
field.Value.ValidationState = Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Valid;
}
}
}
}
public async Task<IActionResult> OnPostProductAsync()
{
ClearFieldErrors(key => key.Contains("MakerName"));
if (!ModelState.IsValid)
{
return Page();
}
return Page();
}
public async Task<IActionResult> OnPostMakerAsync()
{
ClearFieldErrors(key => key.Contains("ProductName"));
if (!ModelState.IsValid)
{
return Page();
}
return Page();
}
Not the best idea because you need to compare the binded field names to strings. I used Contains
because the field keys are inconsistent and sometimes contain a prefix. Was good enough for me because I had small forms, with distinct field names.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With