Validation and Validation Attributes

In this lesson, we will learn how to perform user input validation on the server-side.

In the previous lessons we learned that binders perform validation on the right string format of the input parameters during action parameters binding. Actually, they just try to convert the input strings into the types of the target parameters. When the conversion fails errors are added to the controller ModelState. Other validation checks can be added to each property of a target ViewModel by decorating it with particular data annotations inherited from the ValidationAttribute class. Moreover, the action method code can perform further “manual checks” adding all errors to the ModelState.

Each possible validation option is discussed in detail in a dedicated lesson section.

Wrong format errors

For each wrong formatted input, the binder that processes it adds an entry to the ModelState. Its key is the path in the target object tree or the name of a simple type parameter and whose value is the list of the messages associated with all errors discovered on the input string.

The attempted string value is placed in a property of this entry, too. This way the attempted value can be used to fill the same input field it comes from when the same page is returned to the user so that it can correct all errors.

Thus for instance, if the date string matched with the Salary property of a customer is ill-formatted, such as a decimal value of say “100%.2”, then an entry is created as follows:

  • The entry key is “Salary”.
  • The error messages list will contain a unique message like “The field ‘Salary’ must be a number”.
  • The attempted value is “100%.2”.

Entries in the ModelState are created for each input field that is used to fill a property or a parameter when no error is detected, but, when there are no errors the list of errors is empty.

When the controller returns a view for enabling the user to correct all errors, the “Salary” key is used to locate the input in error. The attempted value is used to fill the input, while the first message in the list of all error messages is used to populate the error label associated with the input in error. The whole list of errors is shown together with all other errors in the validation summary (if any).

Saving the attempted value is very important in case of ill-formatted inputs since the binder is not able to fill the target property, so the attempted value is the only source that can be used to fill the input to be shown to the user.

Important! If when the ModelState is valid, we need to return the same view that was submitted, then clear the ModelState with ModelState.Clear() so that all attempted values are deleted.

Otherwise, the attempted value will be preferred to the actual property value, and this might cause problems. To see why rendering the attempted value might cause problems, suppose, for instance, that the user enters 7.5 in a decimal field, and that there are no validation errors. Then, 7.5 will be inserted in the target property. Now suppose that, for some reason, the action method increments the target property of 1, so that the target property value becomes 8.5, and suppose also that the action method returns the same view that was submitted to the action method. Then, the value shown will be the attempted value that is 7.5, instead of the updated 8.5 value. ModelState.Clear() prevents similar problems.

Each target type has a default validation message associated with it but we may customize each of them through the options parameter of the AddControllersWithViews call in the ConfigureServices method of Startup.cs, as shown below:

Get hands-on with 1200+ tech skills courses.