The ModelBinding process of aspnet core is supported by the model validation process to ensure the success of the modelbinding.
As you might recall, Model data is parsed from the ModelBinder and a step by step process to determine where the data comes from and how it should be used. Yet this cannot ensure semantical correctness, especially if users provide data. So MVC allows for something called ModelValidation.

ModelValidation therefore makes sure that the input data can be bound to the declared model of your application. And provides useful information if this is not the case.

This is important to hold up the integrity of the domain model. Or in other words, that no invalid data is introduced to your application.

What this post will cover

  • ModelState validation
  • Self validating models
  • Action validation in controller

What this post will not cover

  • User notification with tag helpers
  • Client side validation

We will use the following Model for this post:

namespace ModelValidation.Models
{
public class MyModel 
{
public string ModelName { get; set; }
public DateTime Date { get; set; }
}
}

 

We also going to need a controller to use this model with:

namespace ModelValidation.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
=> View();
[HttpPost]
public IActionResult Apply([FromBody] MyModel model)
{
return RedirectTo(nameof(Index));
} 
}
}

The general need for validation stems from the above mentioned purposes, but especially from the need for uncorrupted data in your database which holds up data integrity and with it the long term success of your application.

To apply validation we have the following options:

  1. Explicit model validation
  2. Self validating models
  3. Action method validation

We will look at those three items in this post and start with the explicit validation of a model.

1.) Explicit model validation

The most direct way to validate your input data is to validate it directly and manually in the action method where the data will be used.
We use an MVC controller here, you could also use an API controller, But instead of a view result you would return a BadRequestObjectResult with the model state in it (response 400)

[HttpPost]
public ViewResult Apply([FromBody] MyModel model)
{
if (string.IsNullOrEmpty(model.ModelName))
{
ModelState.AddModelError(nameof(MyModel.ModelName), "Please enter your name");
}
if (ModelState.GetValidationState("Date") 
== ModelValidationState.Valid && DateTime.Now > model.Date)
{ 
ModelState.AddModelError(nameof(appt.Date),
"Please enter a date in the future");
}
if (ModelState.IsValid)
{
return RedirectToAction(nameof(Index));
}
}
...

In essence it checks the values the model binder applied from the request body. If any non valid values are found, the ModelState of the controller base class is used to register those errors. The ModelState instance is of Type ModelStateDictionary.

The ModelStateDictionary also allows for GetValidationState(<propertyname>) which is used to determine whether there are model validation errors, expressed as ModelValidationState enum. As a next property the ModelStateDictionary exposes an IsValid property, that returns true if all bound model properties are valid.

ModelValidationStates:

  • Unvalidated
  • Valid
  • Invalid
  • Skipped

Specifying Validation Rules using Metadata

Action method validation is not optimal, because of possible duplication for all methods where it is used. You could encapsulate it, but a somewhat better and more explicit way to validate your models is to use Attributes.

The attributes are applied on the properties of the Model:

public class MyModel
{
[Required]
public string ModelName {get; set;}
[Required]
public DateTime Date {get; set;}
}

Other Attributes are

  • Range
    numeric value (or IComparable) is in specified scope
  • Compare
    for emails, passwords etc
  • RegularExpression
    match regex pattern, (?i) for ignore case, full pattern must match
  • Required
    Value must not be empty is ensured by this attribute
  • StringLength
    String value must not be longer than the specified length

All have the ability to specify a custom error message, by setting a value for the ErrorMessage property.

Remote validation

Remote validation is essentially a form of client side validation that invokes action methods on the server to perform validation.
A quite common use case for this is: UserName/Email or any other identification mechanism already in use by the application. The Client then sends an AJAX request and the server validates the requested data.

This has some benefits compared to “standard” model validation:

  • it is lightweight on the server side
  • client side mechanisms still apply
  • with an AJAX call you get asynchronous background validation

To create remote validation we create a controller method that Validates one property

...
public JsonResult ValidateDate(string date)
{
if (!DateTime.TryParse(Date, out parsedDate)) 
{
return Json("Please enter a valid date (mm/dd/yyyy)");
} 
else if (DateTime.Now > parsedDate)
{
return Json("Please enter a date in the future");
} 
else 
{
return Json(true);
}
}
...

Remote validation must return JsonResult. Always try to use a string parameter for remote validation, else if model binder would fail the exception would not be catched and validation would return and not work properly.

To fully enable Remote validation apply the Remote Attribute to the Modelproperty Date

...
[Required]
[Remote("ValidateDate", "Home")]
public DateTime Date {get; set;}
...

Summary

In this post we explored the way AspNet core validates the data it parses from the provided model binders.

For this to work we can utilize different approaches, from manually checking the entries in the ModelStateDictionary to simply using the base controller provided features.
Another feature we looked at was the use of self validating models via the provided attributes.

We also looked briefly at remote validation via AJAX calls.


0 Comments

Leave a Reply

Avatar placeholder