How to Use ModelState Validation in ASP.NET Core Web API
Posted by Code Maze | Updated Date Apr 1, 2022 | 9
In this article, we are going to talk about what is ModelState validation, why, and how to use it in ASP.NET Core.
We always strive to separate concerns and decouple different application layers when designing our APIs. Since client input is the first thing that comes to our web API, we usually want to validate that input before further processing. Also, we want to separate that processing from the rest of the application. Let’s see how we can achieve that in ASP.NET Core.
What is a ModelState C#?
When we talk about ModelState , we mean ModelState property of the ControllerBase abstract class in the Microsoft.AspNetCore.Mvc namespace.
It is of ModelStateDictionary type and it represents errors that come from two subsystems: model binding and model validation. The model binding arises errors that can occur when attempting to bind values from an HTTP request to an action method, generally data conversion errors. After the model binding comes model validation, the moment when we check the business rules of our domain.
Let’s explain that theory with one small example:
For example, the model binding logic will check that proper value types are assigned to each property. That means we can’t bind the following JSON to our CreateBookInputModel since it is going to fail because the Title is not a number:
On the other hand, checking that the ISBN string is a 10 or 13 digit number that conforms to a particular digit calculation could be part of the model validation logic.
ModelState Validation Setup
We are going to create our examples with Visual Studio 2022 and the ASP.NET Core Web API project template.
After creating the app, we are going to rename WeatherForecastController to BooksController and change the boilerplate code:
Furthermore, we are going to place our CreateBookInputModel class in the Models folder and delete WeatherForecast class.
Also, let’s create one POST endpoint in our BooksController :
For the sake of simplicity, our controller method is going to return the OK result with the same object it has received. We are not going to have an infrastructure layer in this article or work with 201 status codes for the POST action. The main focus is on the ModelState validation.
Since our BookController inherits ControllerBase we are able to access ModelState property in our Post method, but we are not going to do that yet. Before going further with our example, let’s explain how validation works.
What Does ModelState Validation Validate?
Model binding and model validation occur before executing a controller action in our APIs. Moreover, the ModelState object has an IsValid property where we can check its state. This property is exposed since it should be an application’s responsibility to review ModelState and act accordingly. We are going to see later how the framework can help us with that.
Adding the Validation Rules to the Model
Until now, we have seen just one example of model binding validation. And that was the one we got with defining our properties in the model binding class.
[Required] , [MaxLength(250)] and [StringLength(13, MinimumLength = 13)] are validation attributes from the System.ComponentModel.DataAnnotations namespace, and let us specify validation rules for model properties. If we can’t find any appropriate built-in attributes we can write custom validation attributes as well.
It is worth mentioning that we could also define our Title without [Required] attribute:
public string Title
Since non-nullable properties are treated as if they had a [Required(AllowEmptyStrings = true)] attribute, missing Title in the request will also result in a validation error. We can override this behavior in our Program class:
Sometimes, it is good to separate validation logic from the binding models. We could achieve that by using the FluentValidation library. You can read more about it in our FluentValidation in ASP.NET Core article.
Getting the Validation Errors for Invalid Requests
Let’s see validation in action. We are going to run our solution and make a POST request via Postman with the request body:
After sending the request, we get validation errors with 400 Bad Request status code in the response:
After resending the same request we now get the response with the changed error message:
We have stated that it is an application’s responsibility to check ModelState and act accordingly. But in our code, we don’t check ModelState anywhere. Yet, we’ve got validation errors.
That’s possible because our controller has an [ApiController] attribute. One of its functionalities is to validate ModelState for us and return an automatic HTTP 400 response.
However, sometimes we don’t want to rely on automatic responses. For example, when the model is invalid, the proper status code should be 422 Unprocessable Entity .
Let’s see how we can achieve that.
Manually Invoking ModelState Validation
There are multiple ways of achieving the “manual” ModelState validation. Since we can access ModelState from our controller, the first way is to check the ModelState.IsValid property from our controller method:
We can do better. If our controller ends up having, e.g., five endpoints, we will have some duplicated code. That leads us to the second way of doing the validation, using action filters. Let’s implement one simple action filter:
Now we are ready to remove the validation code from our method and use our validation filter as a service:
But if we try to run our solution and make a request, we will still get an automatic 400 error response. One idea to fix this could be to remove the [ApiController] attribute, and yes, our code will work. However, we shouldn’t do that since this attribute also brings other functionalities to our controller.
So, we are going to fix this differently by disabling the automatic validation in our Program class:
Now, if we run the same request as before via Postman:
We can see that our input was validated, and we receive the 422 Unprocessable Entity status code.
Adding the Custom Validation Logic to the Controller
Apart from the IsValid property we can also access ModelState.AddModelError method from the controller. It allows us to implement custom business logic for our model. For example, we could add interdependency between the Title and Description properties:
Conclusion
In this article, we have explained what is a ModelState validation. Also, we have shown different ways of doing validation in our API and how to override automatic validation provided by [ApiController] attribute.
Model validation in ASP.NET Core MVC and Razor Pages
This article explains how to validate user input in an ASP.NET Core MVC or Razor Pages app.
Model state
Model state represents errors that come from two subsystems: model binding and model validation. Errors that originate from model binding are generally data conversion errors. For example, an «x» is entered in an integer field. Model validation occurs after model binding and reports errors where data doesn’t conform to business rules. For example, a 0 is entered in a field that expects a rating between 1 and 5.
Both model binding and model validation occur before the execution of a controller action or a Razor Pages handler method. For web apps, it’s the app’s responsibility to inspect ModelState.IsValid and react appropriately. Web apps typically redisplay the page with an error message, as shown in the following Razor Pages example:
For ASP.NET Core MVC with controllers and views, the following example shows how to check ModelState.IsValid inside of a controller action:
Web API controllers don’t have to check ModelState.IsValid if they have the [ApiController] attribute. In that case, an automatic HTTP 400 response containing error details is returned when model state is invalid. For more information, see Automatic HTTP 400 responses.
Rerun validation
Validation is automatic, but you might want to repeat it manually. For example, you might compute a value for a property and want to rerun validation after setting the property to the computed value. To rerun validation, call ModelStateDictionary.ClearValidationState to clear validation specific to the model being validated followed by TryValidateModel :
Validation attributes
Validation attributes let you specify validation rules for model properties. The following example from the sample app shows a model class that is annotated with validation attributes. The [ClassicMovie] attribute is a custom validation attribute and the others are built in. Not shown is [ClassicMovieWithClientValidator] , which shows an alternative way to implement a custom attribute.
Built-in attributes
Here are some of the built-in validation attributes:
- [ValidateNever]: Indicates that a property or parameter should be excluded from validation.
- [CreditCard]: Validates that the property has a credit card format. Requires jQuery Validation Additional Methods.
- [Compare]: Validates that two properties in a model match.
- [EmailAddress]: Validates that the property has an email format.
- [Phone]: Validates that the property has a telephone number format.
- [Range]: Validates that the property value falls within a specified range.
- [RegularExpression]: Validates that the property value matches a specified regular expression.
- [Required]: Validates that the field isn’t null. See [Required] attribute for details about this attribute’s behavior.
- [StringLength]: Validates that a string property value doesn’t exceed a specified length limit.
- [Url]: Validates that the property has a URL format.
- [Remote]: Validates input on the client by calling an action method on the server. See [Remote] attribute for details about this attribute’s behavior.
A complete list of validation attributes can be found in the System.ComponentModel.DataAnnotations namespace.
Error messages
Validation attributes let you specify the error message to be displayed for invalid input. For example:
Internally, the attributes call String.Format with a placeholder for the field name and sometimes additional placeholders. For example:
When applied to a Name property, the error message created by the preceding code would be «Name length must be between 6 and 8.».
To find out which parameters are passed to String.Format for a particular attribute’s error message, see the DataAnnotations source code.
Use JSON property names in validation errors
By default, when a validation error occurs, model validation produces a ModelStateDictionary with the property name as the error key. Some apps, such as single page apps, benefit from using JSON property names for validation errors generated from Web APIs. The following code configures validation to use the SystemTextJsonValidationMetadataProvider to use JSON property names:
The following code configures validation to use the NewtonsoftJsonValidationMetadataProvider to use JSON property name when using Json.NET:
For an example of the policy to use camel-casing, see Program.cs on GitHub.
Non-nullable reference types and [Required] attribute
The validation system treats non-nullable parameters or bound properties as if they had a [Required(AllowEmptyStrings = true)] attribute. By enabling Nullable contexts, MVC implicitly starts validating non-nullable properties or parameters as if they had been attributed with the [Required(AllowEmptyStrings = true)] attribute. Consider the following code:
If the app was built with enable , a missing value for Name in a JSON or form post results in a validation error. Use a nullable reference type to allow null or missing values to be specified for the Name property:
[Required] validation on the server
On the server, a required value is considered missing if the property is null. A non-nullable field is always valid, and the [Required] attribute’s error message is never displayed.
However, model binding for a non-nullable property may fail, resulting in an error message such as The value » is invalid . To specify a custom error message for server-side validation of non-nullable types, you have the following options:
Make the field nullable (for example, decimal? instead of decimal ). Nullable value types are treated like standard nullable types.
Specify the default error message to be used by model binding, as shown in the following example:
For more information about model binding errors that you can set default messages for, see DefaultModelBindingMessageProvider.
[Required] validation on the client
Non-nullable types and strings are handled differently on the client compared to the server. On the client:
- A value is considered present only if input is entered for it. Therefore, client-side validation handles non-nullable types the same as nullable types.
- Whitespace in a string field is considered valid input by the jQuery Validation required method. Server-side validation considers a required string field invalid if only whitespace is entered.
As noted earlier, non-nullable types are treated as though they had a [Required(AllowEmptyStrings = true)] attribute. That means you get client-side validation even if you don’t apply the [Required(AllowEmptyStrings = true)] attribute. But if you don’t use the attribute, you get a default error message. To specify a custom error message, use the attribute.
[Remote] attribute
The [Remote] attribute implements client-side validation that requires calling a method on the server to determine whether field input is valid. For example, the app may need to verify whether a user name is already in use.
To implement remote validation:
Create an action method for JavaScript to call. The jQuery Validation remote method expects a JSON response:
- true means the input data is valid.
- false , undefined , or null means the input is invalid. Display the default error message.
- Any other string means the input is invalid. Display the string as a custom error message.
Here’s an example of an action method that returns a custom error message:
In the model class, annotate the property with a [Remote] attribute that points to the validation action method, as shown in the following example:
Additional fields
The AdditionalFields property of the [Remote] attribute lets you validate combinations of fields against data on the server. For example, if the User model had FirstName and LastName properties, you might want to verify that no existing users already have that pair of names. The following example shows how to use AdditionalFields :
AdditionalFields could be set explicitly to the strings «FirstName» and «LastName», but using the nameof operator simplifies later refactoring. The action method for this validation must accept both firstName and lastName arguments:
When the user enters a first or last name, JavaScript makes a remote call to see if that pair of names has been taken.
To validate two or more additional fields, provide them as a comma-delimited list. For example, to add a MiddleName property to the model, set the [Remote] attribute as shown in the following example:
AdditionalFields , like all attribute arguments, must be a constant expression. Therefore, don’t use an interpolated string or call Join to initialize AdditionalFields .
Alternatives to built-in attributes
If you need validation not provided by built-in attributes, you can:
Custom attributes
For scenarios that the built-in validation attributes don’t handle, you can create custom validation attributes. Create a class that inherits from ValidationAttribute, and override the IsValid method.
The IsValid method accepts an object named value, which is the input to be validated. An overload also accepts a ValidationContext object, which provides additional information, such as the model instance created by model binding.
The following example validates that the release date for a movie in the Classic genre isn’t later than a specified year. The [ClassicMovie] attribute:
- Is only run on the server.
- For Classic movies, validates the release date:
The movie variable in the preceding example represents a Movie object that contains the data from the form submission. When validation fails, a ValidationResult with an error message is returned.
IValidatableObject
The preceding example works only with Movie types. Another option for class-level validation is to implement IValidatableObject in the model class, as shown in the following example:
Top-level node validation
Top-level nodes include:
- Action parameters
- Controller properties
- Page handler parameters
- Page model properties
Model-bound top-level nodes are validated in addition to validating model properties. In the following example from the sample app, the VerifyPhone method uses the RegularExpressionAttribute to validate the phone action parameter:
Top-level nodes can use BindRequiredAttribute with validation attributes. In the following example from the sample app, the CheckAge method specifies that the age parameter must be bound from the query string when the form is submitted:
In the Check Age page ( CheckAge.cshtml ), there are two forms. The first form submits an Age value of 99 as a query string parameter: https://localhost:5001/Users/CheckAge?Age=99 .
When a properly formatted age parameter from the query string is submitted, the form validates.
The second form on the Check Age page submits the Age value in the body of the request, and validation fails. Binding fails because the age parameter must come from a query string.
Maximum errors
Validation stops when the maximum number of errors is reached (200 by default). You can configure this number with the following code in Program.cs :
Maximum recursion
ValidationVisitor traverses the object graph of the model being validated. For models that are deep or are infinitely recursive, validation may result in stack overflow. MvcOptions.MaxValidationDepth provides a way to stop validation early if the visitor recursion exceeds a configured depth. The default value of MvcOptions.MaxValidationDepth is 32.
Automatic short-circuit
Validation is automatically short-circuited (skipped) if the model graph doesn’t require validation. Objects that the runtime skips validation for include collections of primitives (such as byte[] , string[] , Dictionary ) and complex object graphs that don’t have any validators.
Client-side validation
Client-side validation prevents submission until the form is valid. The Submit button runs JavaScript that either submits the form or displays error messages.
Client-side validation avoids an unnecessary round trip to the server when there are input errors on a form. The following script references in _Layout.cshtml and _ValidationScriptsPartial.cshtml support client-side validation:
The jQuery Unobtrusive Validation script is a custom Microsoft front-end library that builds on the popular jQuery Validation plugin. Without jQuery Unobtrusive Validation, you would have to code the same validation logic in two places: once in the server-side validation attributes on model properties, and then again in client-side scripts. Instead, Tag Helpers and HTML helpers use the validation attributes and type metadata from model properties to render HTML 5 data- attributes for the form elements that need validation. jQuery Unobtrusive Validation parses the data- attributes and passes the logic to jQuery Validation, effectively «copying» the server-side validation logic to the client. You can display validation errors on the client using tag helpers as shown here:
The preceding tag helpers render the following HTML:
Notice that the data- attributes in the HTML output correspond to the validation attributes for the Movie.ReleaseDate property. The data-val-required attribute contains an error message to display if the user doesn’t fill in the release date field. jQuery Unobtrusive Validation passes this value to the jQuery Validation required() method, which then displays that message in the accompanying element.
Data type validation is based on the .NET type of a property, unless that is overridden by a [DataType] attribute. Browsers have their own default error messages, but the jQuery Validation Unobtrusive Validation package can override those messages. [DataType] attributes and subclasses such as [EmailAddress] let you specify the error message.
Unobtrusive validation
For information on unobtrusive validation, see this GitHub issue.
Add Validation to Dynamic Forms
jQuery Unobtrusive Validation passes validation logic and parameters to jQuery Validation when the page first loads. Therefore, validation doesn’t work automatically on dynamically generated forms. To enable validation, tell jQuery Unobtrusive Validation to parse the dynamic form immediately after you create it. For example, the following code sets up client-side validation on a form added via AJAX.
The $.validator.unobtrusive.parse() method accepts a jQuery selector for its one argument. This method tells jQuery Unobtrusive Validation to parse the data- attributes of forms within that selector. The values of those attributes are then passed to the jQuery Validation plugin.
Add Validation to Dynamic Controls
The $.validator.unobtrusive.parse() method works on an entire form, not on individual dynamically generated controls, such as and . To reparse the form, remove the validation data that was added when the form was parsed earlier, as shown in the following example:
Custom client-side validation
Custom client-side validation is done by generating data- HTML attributes that work with a custom jQuery Validation adapter. The following sample adapter code was written for the [ClassicMovie] and [ClassicMovieWithClientValidator] attributes that were introduced earlier in this article:
For information about how to write adapters, see the jQuery Validation documentation.
The use of an adapter for a given field is triggered by data- attributes that:
- Flag the field as being subject to validation ( data-val=»true» ).
- Identify a validation rule name and error message text (for example, data-val-rulename=»Error message.» ).
- Provide any additional parameters the validator needs (for example, data-val-rulename-param1=»value» ).
The following example shows the data- attributes for the sample app’s ClassicMovie attribute:
As noted earlier, Tag Helpers and HTML helpers use information from validation attributes to render data- attributes. There are two options for writing code that results in the creation of custom data- HTML attributes:
- Create a class that derives from AttributeAdapterBase and a class that implements IValidationAttributeAdapterProvider, and register your attribute and its adapter in DI. This method follows the single responsibility principle in that server-related and client-related validation code is in separate classes. The adapter also has the advantage that since it’s registered in DI, other services in DI are available to it if needed.
- Implement IClientModelValidator in your ValidationAttribute class. This method might be appropriate if the attribute doesn’t do any server-side validation and doesn’t need any services from DI.
AttributeAdapter for client-side validation
This method of rendering data- attributes in HTML is used by the ClassicMovie attribute in the sample app. To add client validation by using this method:
Create an attribute adapter class for the custom validation attribute. Derive the class from AttributeAdapterBase . Create an AddValidation method that adds data- attributes to the rendered output, as shown in this example:
Create an adapter provider class that implements IValidationAttributeAdapterProvider. In the GetAttributeAdapter method pass in the custom attribute to the adapter’s constructor, as shown in this example:
Register the adapter provider for DI in Program.cs :
IClientModelValidator for client-side validation
This method of rendering data- attributes in HTML is used by the ClassicMovieWithClientValidator attribute in the sample app. To add client validation by using this method:
In the custom validation attribute, implement the IClientModelValidator interface and create an AddValidation method. In the AddValidation method, add data- attributes for validation, as shown in the following example:
Disable client-side validation
The following code disables client validation in Razor Pages:
Other options to disable client-side validation:
- Comment out the reference to _ValidationScriptsPartial in all the .cshtml files.
- Remove the contents of the Pages\Shared_ValidationScriptsPartial.cshtml file.
The preceding approach won’t prevent client-side validation of ASP.NET Core Identity Razor Class Library. For more information, see Scaffold Identity in ASP.NET Core projects.
Additional resources
This article explains how to validate user input in an ASP.NET Core MVC or Razor Pages app.
Model state
Model state represents errors that come from two subsystems: model binding and model validation. Errors that originate from model binding are generally data conversion errors. For example, an «x» is entered in an integer field. Model validation occurs after model binding and reports errors where data doesn’t conform to business rules. For example, a 0 is entered in a field that expects a rating between 1 and 5.
Both model binding and model validation occur before the execution of a controller action or a Razor Pages handler method. For web apps, it’s the app’s responsibility to inspect ModelState.IsValid and react appropriately. Web apps typically redisplay the page with an error message, as shown in the following Razor Pages example:
For ASP.NET Core MVC with controllers and views, the following example shows how to check ModelState.IsValid inside of a controller action:
Web API controllers don’t have to check ModelState.IsValid if they have the [ApiController] attribute. In that case, an automatic HTTP 400 response containing error details is returned when model state is invalid. For more information, see Automatic HTTP 400 responses.
Rerun validation
Validation is automatic, but you might want to repeat it manually. For example, you might compute a value for a property and want to rerun validation after setting the property to the computed value. To rerun validation, call ModelStateDictionary.ClearValidationState to clear validation specific to the model being validated followed by TryValidateModel :
Validation attributes
Validation attributes let you specify validation rules for model properties. The following example from the sample app shows a model class that is annotated with validation attributes. The [ClassicMovie] attribute is a custom validation attribute and the others are built in. Not shown is [ClassicMovieWithClientValidator] , which shows an alternative way to implement a custom attribute.
Built-in attributes
Here are some of the built-in validation attributes:
- [ValidateNever]: Indicates that a property or parameter should be excluded from validation.
- [CreditCard]: Validates that the property has a credit card format. Requires jQuery Validation Additional Methods.
- [Compare]: Validates that two properties in a model match.
- [EmailAddress]: Validates that the property has an email format.
- [Phone]: Validates that the property has a telephone number format.
- [Range]: Validates that the property value falls within a specified range.
- [RegularExpression]: Validates that the property value matches a specified regular expression.
- [Required]: Validates that the field isn’t null. See [Required] attribute for details about this attribute’s behavior.
- [StringLength]: Validates that a string property value doesn’t exceed a specified length limit.
- [Url]: Validates that the property has a URL format.
- [Remote]: Validates input on the client by calling an action method on the server. See [Remote] attribute for details about this attribute’s behavior.
A complete list of validation attributes can be found in the System.ComponentModel.DataAnnotations namespace.
Error messages
Validation attributes let you specify the error message to be displayed for invalid input. For example:
Internally, the attributes call String.Format with a placeholder for the field name and sometimes additional placeholders. For example:
When applied to a Name property, the error message created by the preceding code would be «Name length must be between 6 and 8.».
To find out which parameters are passed to String.Format for a particular attribute’s error message, see the DataAnnotations source code.
Non-nullable reference types and the [Required] attribute
The validation system treats non-nullable parameters or bound properties as if they had a [Required(AllowEmptyStrings = true)] attribute. By enabling Nullable contexts, MVC implicitly starts validating non-nullable properties on non-generic types or parameters as if they had been attributed with the [Required(AllowEmptyStrings = true)] attribute. Consider the following code:
If the app was built with enable , a missing value for Name in a JSON or form post results in a validation error. Use a nullable reference type to allow null or missing values to be specified for the Name property:
Non-nullable properties on generic types and [Required] attribute
Non-nullable properties on generic types must include the [Required] attribute when the type is required. In the following code, TestRequired is not required:
In the following code, TestRequired is explicitly marked as required:
[Required] validation on the server
On the server, a required value is considered missing if the property is null. A non-nullable field is always valid, and the [Required] attribute’s error message is never displayed.
However, model binding for a non-nullable property may fail, resulting in an error message such as The value » is invalid . To specify a custom error message for server-side validation of non-nullable types, you have the following options:
Make the field nullable (for example, decimal? instead of decimal ). Nullable value types are treated like standard nullable types.
Specify the default error message to be used by model binding, as shown in the following example:
For more information about model binding errors that you can set default messages for, see DefaultModelBindingMessageProvider.
[Required] validation on the client
Non-nullable types and strings are handled differently on the client compared to the server. On the client:
- A value is considered present only if input is entered for it. Therefore, client-side validation handles non-nullable types the same as nullable types.
- Whitespace in a string field is considered valid input by the jQuery Validation required method. Server-side validation considers a required string field invalid if only whitespace is entered.
As noted earlier, non-nullable types are treated as though they had a [Required(AllowEmptyStrings = true)] attribute. That means you get client-side validation even if you don’t apply the [Required(AllowEmptyStrings = true)] attribute. But if you don’t use the attribute, you get a default error message. To specify a custom error message, use the attribute.
[Remote] attribute
The [Remote] attribute implements client-side validation that requires calling a method on the server to determine whether field input is valid. For example, the app may need to verify whether a user name is already in use.
To implement remote validation:
Create an action method for JavaScript to call. The jQuery Validation remote method expects a JSON response:
- true means the input data is valid.
- false , undefined , or null means the input is invalid. Display the default error message.
- Any other string means the input is invalid. Display the string as a custom error message.
Here’s an example of an action method that returns a custom error message:
In the model class, annotate the property with a [Remote] attribute that points to the validation action method, as shown in the following example:
Additional fields
The AdditionalFields property of the [Remote] attribute lets you validate combinations of fields against data on the server. For example, if the User model had FirstName and LastName properties, you might want to verify that no existing users already have that pair of names. The following example shows how to use AdditionalFields :
AdditionalFields could be set explicitly to the strings «FirstName» and «LastName», but using the nameof operator simplifies later refactoring. The action method for this validation must accept both firstName and lastName arguments:
When the user enters a first or last name, JavaScript makes a remote call to see if that pair of names has been taken.
To validate two or more additional fields, provide them as a comma-delimited list. For example, to add a MiddleName property to the model, set the [Remote] attribute as shown in the following example:
AdditionalFields , like all attribute arguments, must be a constant expression. Therefore, don’t use an interpolated string or call Join to initialize AdditionalFields .
Alternatives to built-in attributes
If you need validation not provided by built-in attributes, you can:
Custom attributes
For scenarios that the built-in validation attributes don’t handle, you can create custom validation attributes. Create a class that inherits from ValidationAttribute, and override the IsValid method.
The IsValid method accepts an object named value, which is the input to be validated. An overload also accepts a ValidationContext object, which provides additional information, such as the model instance created by model binding.
The following example validates that the release date for a movie in the Classic genre isn’t later than a specified year. The [ClassicMovie] attribute:
- Is only run on the server.
- For Classic movies, validates the release date:
The movie variable in the preceding example represents a Movie object that contains the data from the form submission. When validation fails, a ValidationResult with an error message is returned.
IValidatableObject
The preceding example works only with Movie types. Another option for class-level validation is to implement IValidatableObject in the model class, as shown in the following example:
Top-level node validation
Top-level nodes include:
- Action parameters
- Controller properties
- Page handler parameters
- Page model properties
Model-bound top-level nodes are validated in addition to validating model properties. In the following example from the sample app, the VerifyPhone method uses the RegularExpressionAttribute to validate the phone action parameter:
Top-level nodes can use BindRequiredAttribute with validation attributes. In the following example from the sample app, the CheckAge method specifies that the age parameter must be bound from the query string when the form is submitted:
In the Check Age page ( CheckAge.cshtml ), there are two forms. The first form submits an Age value of 99 as a query string parameter: https://localhost:5001/Users/CheckAge?Age=99 .
When a properly formatted age parameter from the query string is submitted, the form validates.
The second form on the Check Age page submits the Age value in the body of the request, and validation fails. Binding fails because the age parameter must come from a query string.
Maximum errors
Validation stops when the maximum number of errors is reached (200 by default). You can configure this number with the following code in Program.cs :
Maximum recursion
ValidationVisitor traverses the object graph of the model being validated. For models that are deep or are infinitely recursive, validation may result in stack overflow. MvcOptions.MaxValidationDepth provides a way to stop validation early if the visitor recursion exceeds a configured depth. The default value of MvcOptions.MaxValidationDepth is 32.
Automatic short-circuit
Validation is automatically short-circuited (skipped) if the model graph doesn’t require validation. Objects that the runtime skips validation for include collections of primitives (such as byte[] , string[] , Dictionary ) and complex object graphs that don’t have any validators.
Client-side validation
Client-side validation prevents submission until the form is valid. The Submit button runs JavaScript that either submits the form or displays error messages.
Client-side validation avoids an unnecessary round trip to the server when there are input errors on a form. The following script references in _Layout.cshtml and _ValidationScriptsPartial.cshtml support client-side validation:
The jQuery Unobtrusive Validation script is a custom Microsoft front-end library that builds on the popular jQuery Validation plugin. Without jQuery Unobtrusive Validation, you would have to code the same validation logic in two places: once in the server-side validation attributes on model properties, and then again in client-side scripts. Instead, Tag Helpers and HTML helpers use the validation attributes and type metadata from model properties to render HTML 5 data- attributes for the form elements that need validation. jQuery Unobtrusive Validation parses the data- attributes and passes the logic to jQuery Validation, effectively «copying» the server-side validation logic to the client. You can display validation errors on the client using tag helpers as shown here:
The preceding tag helpers render the following HTML:
Notice that the data- attributes in the HTML output correspond to the validation attributes for the Movie.ReleaseDate property. The data-val-required attribute contains an error message to display if the user doesn’t fill in the release date field. jQuery Unobtrusive Validation passes this value to the jQuery Validation required() method, which then displays that message in the accompanying element.
Data type validation is based on the .NET type of a property, unless that is overridden by a [DataType] attribute. Browsers have their own default error messages, but the jQuery Validation Unobtrusive Validation package can override those messages. [DataType] attributes and subclasses such as [EmailAddress] let you specify the error message.
Unobtrusive validation
For information on unobtrusive validation, see this GitHub issue.
Add Validation to Dynamic Forms
jQuery Unobtrusive Validation passes validation logic and parameters to jQuery Validation when the page first loads. Therefore, validation doesn’t work automatically on dynamically generated forms. To enable validation, tell jQuery Unobtrusive Validation to parse the dynamic form immediately after you create it. For example, the following code sets up client-side validation on a form added via AJAX.
The $.validator.unobtrusive.parse() method accepts a jQuery selector for its one argument. This method tells jQuery Unobtrusive Validation to parse the data- attributes of forms within that selector. The values of those attributes are then passed to the jQuery Validation plugin.
Add Validation to Dynamic Controls
The $.validator.unobtrusive.parse() method works on an entire form, not on individual dynamically generated controls, such as and . To reparse the form, remove the validation data that was added when the form was parsed earlier, as shown in the following example:
Custom client-side validation
Custom client-side validation is done by generating data- HTML attributes that work with a custom jQuery Validation adapter. The following sample adapter code was written for the [ClassicMovie] and [ClassicMovieWithClientValidator] attributes that were introduced earlier in this article:
For information about how to write adapters, see the jQuery Validation documentation.
The use of an adapter for a given field is triggered by data- attributes that:
- Flag the field as being subject to validation ( data-val=»true» ).
- Identify a validation rule name and error message text (for example, data-val-rulename=»Error message.» ).
- Provide any additional parameters the validator needs (for example, data-val-rulename-param1=»value» ).
The following example shows the data- attributes for the sample app’s ClassicMovie attribute:
As noted earlier, Tag Helpers and HTML helpers use information from validation attributes to render data- attributes. There are two options for writing code that results in the creation of custom data- HTML attributes:
- Create a class that derives from AttributeAdapterBase and a class that implements IValidationAttributeAdapterProvider, and register your attribute and its adapter in DI. This method follows the single responsibility principle in that server-related and client-related validation code is in separate classes. The adapter also has the advantage that since it’s registered in DI, other services in DI are available to it if needed.
- Implement IClientModelValidator in your ValidationAttribute class. This method might be appropriate if the attribute doesn’t do any server-side validation and doesn’t need any services from DI.
AttributeAdapter for client-side validation
This method of rendering data- attributes in HTML is used by the ClassicMovie attribute in the sample app. To add client validation by using this method:
Create an attribute adapter class for the custom validation attribute. Derive the class from AttributeAdapterBase . Create an AddValidation method that adds data- attributes to the rendered output, as shown in this example:
Create an adapter provider class that implements IValidationAttributeAdapterProvider. In the GetAttributeAdapter method pass in the custom attribute to the adapter’s constructor, as shown in this example:
Register the adapter provider for DI in Program.cs :
IClientModelValidator for client-side validation
This method of rendering data- attributes in HTML is used by the ClassicMovieWithClientValidator attribute in the sample app. To add client validation by using this method:
In the custom validation attribute, implement the IClientModelValidator interface and create an AddValidation method. In the AddValidation method, add data- attributes for validation, as shown in the following example:
Disable client-side validation
The following code disables client validation in Razor Pages:
Other options to disable client-side validation:
- Comment out the reference to _ValidationScriptsPartial in all the .cshtml files.
- Remove the contents of the Pages\Shared_ValidationScriptsPartial.cshtml file.
The preceding approach won’t prevent client-side validation of ASP.NET Core Identity Razor Class Library. For more information, see Scaffold Identity in ASP.NET Core projects.
Additional resources
This article explains how to validate user input in an ASP.NET Core MVC or Razor Pages app.
Model state
Model state represents errors that come from two subsystems: model binding and model validation. Errors that originate from model binding are generally data conversion errors. For example, an «x» is entered in an integer field. Model validation occurs after model binding and reports errors where data doesn’t conform to business rules. For example, a 0 is entered in a field that expects a rating between 1 and 5.
Both model binding and model validation occur before the execution of a controller action or a Razor Pages handler method. For web apps, it’s the app’s responsibility to inspect ModelState.IsValid and react appropriately. Web apps typically redisplay the page with an error message:
Web API controllers don’t have to check ModelState.IsValid if they have the [ApiController] attribute. In that case, an automatic HTTP 400 response containing error details is returned when model state is invalid. For more information, see Automatic HTTP 400 responses.
Rerun validation
Validation is automatic, but you might want to repeat it manually. For example, you might compute a value for a property and want to rerun validation after setting the property to the computed value. To rerun validation, call ModelStateDictionary.ClearValidationState to clear validation specific to the model being validated followed by TryValidateModel :
Validation attributes
Validation attributes let you specify validation rules for model properties. The following example from the sample app shows a model class that is annotated with validation attributes. The [ClassicMovie] attribute is a custom validation attribute and the others are built in. Not shown is [ClassicMovieWithClientValidator] , which shows an alternative way to implement a custom attribute.
Built-in attributes
Here are some of the built-in validation attributes:
- [ValidateNever]: Indicates that a property or parameter should be excluded from validation.
- [CreditCard]: Validates that the property has a credit card format. Requires jQuery Validation Additional Methods.
- [Compare]: Validates that two properties in a model match.
- [EmailAddress]: Validates that the property has an email format.
- [Phone]: Validates that the property has a telephone number format.
- [Range]: Validates that the property value falls within a specified range.
- [RegularExpression]: Validates that the property value matches a specified regular expression.
- [Required]: Validates that the field isn’t null. See [Required] attribute for details about this attribute’s behavior.
- [StringLength]: Validates that a string property value doesn’t exceed a specified length limit.
- [Url]: Validates that the property has a URL format.
- [Remote]: Validates input on the client by calling an action method on the server. See [Remote] attribute for details about this attribute’s behavior.
A complete list of validation attributes can be found in the System.ComponentModel.DataAnnotations namespace.
Error messages
Validation attributes let you specify the error message to be displayed for invalid input. For example:
Internally, the attributes call String.Format with a placeholder for the field name and sometimes additional placeholders. For example:
When applied to a Name property, the error message created by the preceding code would be «Name length must be between 6 and 8.».
To find out which parameters are passed to String.Format for a particular attribute’s error message, see the DataAnnotations source code.
Non-nullable reference types and [Required] attribute
The validation system treats non-nullable parameters or bound properties as if they had a [Required(AllowEmptyStrings = true)] attribute. By enabling Nullable contexts, MVC implicitly starts validating non-nullable properties or parameters as if they had been attributed with the [Required(AllowEmptyStrings = true)] attribute. Consider the following code:
If the app was built with enable , a missing value for Name in a JSON or form post results in a validation error. Use a nullable reference type to allow null or missing values to be specified for the Name property:
This behavior can be disabled by configuring SuppressImplicitRequiredAttributeForNonNullableReferenceTypes in Startup.ConfigureServices :
[Required] validation on the server
On the server, a required value is considered missing if the property is null. A non-nullable field is always valid, and the [Required] attribute’s error message is never displayed.
However, model binding for a non-nullable property may fail, resulting in an error message such as The value » is invalid . To specify a custom error message for server-side validation of non-nullable types, you have the following options:
Make the field nullable (for example, decimal? instead of decimal ). Nullable value types are treated like standard nullable types.
Specify the default error message to be used by model binding, as shown in the following example:
For more information about model binding errors that you can set default messages for, see DefaultModelBindingMessageProvider.
[Required] validation on the client
Non-nullable types and strings are handled differently on the client compared to the server. On the client:
- A value is considered present only if input is entered for it. Therefore, client-side validation handles non-nullable types the same as nullable types.
- Whitespace in a string field is considered valid input by the jQuery Validation required method. Server-side validation considers a required string field invalid if only whitespace is entered.
As noted earlier, non-nullable types are treated as though they had a [Required(AllowEmptyStrings = true)] attribute. That means you get client-side validation even if you don’t apply the [Required(AllowEmptyStrings = true)] attribute. But if you don’t use the attribute, you get a default error message. To specify a custom error message, use the attribute.
[Remote] attribute
The [Remote] attribute implements client-side validation that requires calling a method on the server to determine whether field input is valid. For example, the app may need to verify whether a user name is already in use.
To implement remote validation:
Create an action method for JavaScript to call. The jQuery Validation remote method expects a JSON response:
- true means the input data is valid.
- false , undefined , or null means the input is invalid. Display the default error message.
- Any other string means the input is invalid. Display the string as a custom error message.
Here’s an example of an action method that returns a custom error message:
In the model class, annotate the property with a [Remote] attribute that points to the validation action method, as shown in the following example:
Additional fields
The AdditionalFields property of the [Remote] attribute lets you validate combinations of fields against data on the server. For example, if the User model had FirstName and LastName properties, you might want to verify that no existing users already have that pair of names. The following example shows how to use AdditionalFields :
AdditionalFields could be set explicitly to the strings «FirstName» and «LastName», but using the nameof operator simplifies later refactoring. The action method for this validation must accept both firstName and lastName arguments:
When the user enters a first or last name, JavaScript makes a remote call to see if that pair of names has been taken.
To validate two or more additional fields, provide them as a comma-delimited list. For example, to add a MiddleName property to the model, set the [Remote] attribute as shown in the following example:
AdditionalFields , like all attribute arguments, must be a constant expression. Therefore, don’t use an interpolated string or call Join to initialize AdditionalFields .
Alternatives to built-in attributes
If you need validation not provided by built-in attributes, you can:
Custom attributes
For scenarios that the built-in validation attributes don’t handle, you can create custom validation attributes. Create a class that inherits from ValidationAttribute, and override the IsValid method.
The IsValid method accepts an object named value, which is the input to be validated. An overload also accepts a ValidationContext object, which provides additional information, such as the model instance created by model binding.
The following example validates that the release date for a movie in the Classic genre isn’t later than a specified year. The [ClassicMovie] attribute:
- Is only run on the server.
- For Classic movies, validates the release date:
The movie variable in the preceding example represents a Movie object that contains the data from the form submission. When validation fails, a ValidationResult with an error message is returned.
IValidatableObject
The preceding example works only with Movie types. Another option for class-level validation is to implement IValidatableObject in the model class, as shown in the following example:
Top-level node validation
Top-level nodes include:
- Action parameters
- Controller properties
- Page handler parameters
- Page model properties
Model-bound top-level nodes are validated in addition to validating model properties. In the following example from the sample app, the VerifyPhone method uses the RegularExpressionAttribute to validate the phone action parameter:
Top-level nodes can use BindRequiredAttribute with validation attributes. In the following example from the sample app, the CheckAge method specifies that the age parameter must be bound from the query string when the form is submitted:
In the Check Age page ( CheckAge.cshtml ), there are two forms. The first form submits an Age value of 99 as a query string parameter: https://localhost:5001/Users/CheckAge?Age=99 .
When a properly formatted age parameter from the query string is submitted, the form validates.
The second form on the Check Age page submits the Age value in the body of the request, and validation fails. Binding fails because the age parameter must come from a query string.
Maximum errors
Validation stops when the maximum number of errors is reached (200 by default). You can configure this number with the following code in Startup.ConfigureServices :
Maximum recursion
ValidationVisitor traverses the object graph of the model being validated. For models that are deep or are infinitely recursive, validation may result in stack overflow. MvcOptions.MaxValidationDepth provides a way to stop validation early if the visitor recursion exceeds a configured depth. The default value of MvcOptions.MaxValidationDepth is 32.
Automatic short-circuit
Validation is automatically short-circuited (skipped) if the model graph doesn’t require validation. Objects that the runtime skips validation for include collections of primitives (such as byte[] , string[] , Dictionary ) and complex object graphs that don’t have any validators.
Client-side validation
Client-side validation prevents submission until the form is valid. The Submit button runs JavaScript that either submits the form or displays error messages.
Client-side validation avoids an unnecessary round trip to the server when there are input errors on a form. The following script references in _Layout.cshtml and _ValidationScriptsPartial.cshtml support client-side validation:
The jQuery Unobtrusive Validation script is a custom Microsoft front-end library that builds on the popular jQuery Validation plugin. Without jQuery Unobtrusive Validation, you would have to code the same validation logic in two places: once in the server-side validation attributes on model properties, and then again in client-side scripts. Instead, Tag Helpers and HTML helpers use the validation attributes and type metadata from model properties to render HTML 5 data- attributes for the form elements that need validation. jQuery Unobtrusive Validation parses the data- attributes and passes the logic to jQuery Validation, effectively «copying» the server-side validation logic to the client. You can display validation errors on the client using tag helpers as shown here:
The preceding tag helpers render the following HTML:
Notice that the data- attributes in the HTML output correspond to the validation attributes for the Movie.ReleaseDate property. The data-val-required attribute contains an error message to display if the user doesn’t fill in the release date field. jQuery Unobtrusive Validation passes this value to the jQuery Validation required() method, which then displays that message in the accompanying element.
Data type validation is based on the .NET type of a property, unless that is overridden by a [DataType] attribute. Browsers have their own default error messages, but the jQuery Validation Unobtrusive Validation package can override those messages. [DataType] attributes and subclasses such as [EmailAddress] let you specify the error message.
Unobtrusive validation
For information on unobtrusive validation, see this GitHub issue.
Add Validation to Dynamic Forms
jQuery Unobtrusive Validation passes validation logic and parameters to jQuery Validation when the page first loads. Therefore, validation doesn’t work automatically on dynamically generated forms. To enable validation, tell jQuery Unobtrusive Validation to parse the dynamic form immediately after you create it. For example, the following code sets up client-side validation on a form added via AJAX.
The $.validator.unobtrusive.parse() method accepts a jQuery selector for its one argument. This method tells jQuery Unobtrusive Validation to parse the data- attributes of forms within that selector. The values of those attributes are then passed to the jQuery Validation plugin.
Add Validation to Dynamic Controls
The $.validator.unobtrusive.parse() method works on an entire form, not on individual dynamically generated controls, such as and . To reparse the form, remove the validation data that was added when the form was parsed earlier, as shown in the following example:
Custom client-side validation
Custom client-side validation is done by generating data- HTML attributes that work with a custom jQuery Validation adapter. The following sample adapter code was written for the [ClassicMovie] and [ClassicMovieWithClientValidator] attributes that were introduced earlier in this article:
For information about how to write adapters, see the jQuery Validation documentation.
The use of an adapter for a given field is triggered by data- attributes that:
- Flag the field as being subject to validation ( data-val=»true» ).
- Identify a validation rule name and error message text (for example, data-val-rulename=»Error message.» ).
- Provide any additional parameters the validator needs (for example, data-val-rulename-param1=»value» ).
The following example shows the data- attributes for the sample app’s ClassicMovie attribute:
As noted earlier, Tag Helpers and HTML helpers use information from validation attributes to render data- attributes. There are two options for writing code that results in the creation of custom data- HTML attributes:
- Create a class that derives from AttributeAdapterBase and a class that implements IValidationAttributeAdapterProvider, and register your attribute and its adapter in DI. This method follows the single responsibility principle in that server-related and client-related validation code is in separate classes. The adapter also has the advantage that since it’s registered in DI, other services in DI are available to it if needed.
- Implement IClientModelValidator in your ValidationAttribute class. This method might be appropriate if the attribute doesn’t do any server-side validation and doesn’t need any services from DI.
AttributeAdapter for client-side validation
This method of rendering data- attributes in HTML is used by the ClassicMovie attribute in the sample app. To add client validation by using this method:
Create an attribute adapter class for the custom validation attribute. Derive the class from AttributeAdapterBase . Create an AddValidation method that adds data- attributes to the rendered output, as shown in this example:
Create an adapter provider class that implements IValidationAttributeAdapterProvider. In the GetAttributeAdapter method pass in the custom attribute to the adapter’s constructor, as shown in this example:
Register the adapter provider for DI in Startup.ConfigureServices :
IClientModelValidator for client-side validation
This method of rendering data- attributes in HTML is used by the ClassicMovieWithClientValidator attribute in the sample app. To add client validation by using this method:
In the custom validation attribute, implement the IClientModelValidator interface and create an AddValidation method. In the AddValidation method, add data- attributes for validation, as shown in the following example:
Disable client-side validation
The following code disables client validation in Razor Pages:
Other options to disable client-side validation: