In ASP.NET MVC, I have a model class implementing IValidatableObject and have a property int? Day {get; set;}.
If the user tries to submit a string for this, ModelState is correctly marked as invalid, but the rest of my validation inside the Validate method doesn't run, so the user only sees this one error instead of everything.
I need all errors to be made available at once (it's a really frustrating user experience to get given one error at a time). So I was hoping to either be able to override this behaviour and set Day to be null or hoping that ASP.NET MVC has some built in attribute or something to do this for me (my own validation would then pick up Day as being null and ask the user to correct this).
My ViewModel:
public class FormViewModel : IValidatableObject
{
public int? Day { get; set; }
public IEnumerable<string> ErrorMessageOrdering { get; } = new List<string>()
{
nameof(Date),
};
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// This method doesn't get run when the user inputs "s" for Day. This is NOT the desired behaviour as other validations in this method wont run
if (!Day.HasValue)
{
yield return new ValidationResult("Date must include a day", new[] {nameof(Day)});
}
// ... Validate other fields
}
}
My Controller:
[ServiceFilter(typeof(ConfigSettingsAttribute))]
public class HomeController : Controller
[Route("/form")]
[HttpPost]
public async Task<IActionResult> FormAsync(FormViewModel model)
{
if (!ModelState.IsValid)
{
return View(model)
}
}
}
My View:
@model FormViewModel
@if (!ViewData.ModelState.IsValid)
{
<partial name="_ErrorSummary" model=@(Model.ErrorMessageOrdering) />
}
<form method="post">
<input asp-for="Day" type="text" pattern="[0-9]*" inputmode="numeric" maxlength="2">
@* ... Other fields @*
</form>
When the user enters "s" for the Day input (the numeric html values seem to have no effect on restricting user input btw) the Validate method on the view model is skipped and the ModelState is populated with Day being an invalid field with the message The value 's' is invalid for Day..
When the user doesn't enter anything in the Day input, the Validate method does run and the ModelState is populated with Day being invalid with the message being what I set it to in Validate.
The problem here is that because Validate isn't called on the view model when the user enters a string (presumably because it failed to cast/bind values), any further errors don't get added to ModelState. I would prefer it if the framework set Day to be null if it can't bind instead of short circuiting. If I could override this behaviour that would also be great.
Note that my problem seems to be that Validate is not called if there are bind/casting errors. As opposed to the Validate method not being written correctly. When binding errors dont occur, I do get a list of validation failures as expected