Reputation: 741
In one of my controller actions, the first thing I do is pass the model to a new action that essentially just parses input to determine whether or not the user entered a valid date. The model is then returned and ModelState.IsValid is inspected.
public Import ValidateUploadModel(Import Model)
{
// Do not allow future dates
if (Model.CurrMoInfo.CurrMo > DateTime.Now)
{
ModelState.AddModelError("FutureDate", "You cannot assign a future date.");
}
// Do not allow dates from the same month (we run the processing a month behind)
if (Model.CurrMoInfo.CurrMo.Month == DateTime.Now.Month)
{
ModelState.AddModelError("SameMonth", "You must process a previous month.");
}
// Ensure day is last day of a previous month
if (Model.CurrMoInfo.CurrMo.Day != DateTime.DaysInMonth(Model.CurrMoInfo.CurrMo.Year, Model.CurrMoInfo.CurrMo.Month))
{
ModelState.AddModelError("LastDay", "You must enter the last day of the month.");
}
// Do not allow dates older than 12 months back
if (Model.CurrMoInfo.CurrMo < DateTime.Now.AddMonths(-12))
{
ModelState.AddModelError("TooOld", "Date must not be older than a year.");
}
return Model;
}
At the point where I know I have model state errors, I am able to correctly show them in my razor view by putting the following
<span class="text-danger">@Html.ValidationSummary(false)</span>
So, since all of my model state errors are for the same input on the page I can safely do this. But what if I have various inputs with various errors that I need to display independently of one another? How would I go about doing this? Additionally, is there a better (or more appropriate) way to do this aside from using @Html.ValidationSummary?
I have searched through Microsoft docs and a few dozen StackOverflow questions to try and translate older answers into the .Net Core way of doing things with no luck.
Edit for clarity:
Here is the entire card in the razor view:
<div class="card-body">
@if (Model.StagingCount == 0)
{
<input asp-for="@Model.CurrMoInfo.CurrMo" type="date" required class="col-lg-12" />
}
else
{
<input asp-for="@Model.CurrMoInfo.CurrMo" type="date" disabled="disabled" required class="col-lg-12" />
}
<span class="text-danger">@Html.ValidationSummary(false)</span>
</div>
The input is for a model property however it is not annotated. I've written my own rules and manually add errors to the model state if the rules are not adhered to. The code I have works however it's not scalable when I start needing to validate more fields. I just want to know what a better way of doing this is.
Upvotes: 5
Views: 21788
Reputation: 61784
In the example above, you are not really following standard practice.
For simple validations like this (where you're only validating the value in one field) the key you use to place the error message against in the ModelState is supposed to be the same as the name of the affected field in the model. In your case, really all your errors should be logged against the CurrMoInfo.CurrMo
key. Only the error message itself needs to differ. Using a custom key for each specific different error doesn't add any value to your application as far as I can tell. You're not using it the way it was intended.
If you log them all against CurrMoInfo.CurrMo
then you can use an asp-validation-for
tag helper to create a field which displays errors specifically for that field, e.g.
<span asp-validation-for="CurrMoInfo.CurrMo" class="text-danger"></span>
You can then (optionally) use a ValidationSummary to (as the title suggests) summarise all the errors for the whole model - and to display any extra model errors you may have created which don't relate to a single specific field.
Complete example:
public Import ValidateUploadModel(Import Model)
{
// DO not allow future dates
if (Model.CurrMoInfo.CurrMo > DateTime.Now)
{
ModelState.AddModelError("CurrMoInfo.CurrMo", "You cannot assign a future date.");
}
//Do not allow dates from the same month (we run the processing a month behind)
if (Model.CurrMoInfo.CurrMo.Month == DateTime.Now.Month)
{
ModelState.AddModelError("CurrMoInfo.CurrMo", "You must process a previous month.");
}
//Ensure day is last day of a previous month
if (Model.CurrMoInfo.CurrMo.Day != DateTime.DaysInMonth(Model.CurrMoInfo.CurrMo.Year, Model.CurrMoInfo.CurrMo.Month))
{
ModelState.AddModelError("CurrMoInfo.CurrMo", "You must enter the last day of the month.");
}
//Do not allow dates older than 12 months back
if (Model.CurrMoInfo.CurrMo < DateTime.Now.AddMonths(-12))
{
ModelState.AddModelError("CurrMoInfo.CurrMo", "Date must not be older than a year.");
}
return Model;
}
<div class="card-body">
@if (Model.StagingCount == 0)
{
<input asp-for="CurrMoInfo.CurrMo" type="date" required class="col-lg-12" />
}
else
{
<input asp-for="CurrMoInfo.CurrMo" type="date" disabled="disabled" required class="col-lg-12" />
}
<span asp-validation-for="CurrMoInfo.CurrMo" class="text-danger"></span>
</div>
Further reading: https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/validation?view=aspnetcore-2.2
P.S. I don't think this general principle has changed from .NET Framework to .NET Core.
Upvotes: 7