Overview
- Model state represents errors that come from either the model binding or model validation subsystems.
 - Model validation occurs after model binding and reports errors where the data does not conform to business rules (like a 0 in a field that expects a value from 1 to 5).
 - Both model binding and model validation occur before a controller action method, or a Razor Pages handler method, executes.
 - When a validation error occurs, model validation generates a 
ModelStateDictionarywith the property name as the error key. - For web apps, 
the app must inspect and react appropriately.ModelState.IsValid 
Example for MVC app (controllers and views):
public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
        return View(movie);
    _context.Movies.Add(movie);
    await _context.SaveChangesAsync();
    return RedirectToAction(nameof(Index));
}
Note: Web API controllers do not need to check ModelState.IsValid if they are decorated with [ApiController] — assuming
automatic HTTP 400 responses are enabled (default).
repeating validation
Although validation is automatic, it can be re-run if needed by calling ModelStateDictionary.ClearValidationState and then TryValidateModel:
public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;
    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
        return Page();
    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();
    return RedirectToPage("./Index");
}
validation attributes
Use to define validation rules for model properties:
public class Movie
{
    // ...
    
    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }
    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;
    [Range(0, 999.99)]
    public decimal Price { get; set; }
    
    // ...
}
See also: System.ComponentModel.DataValidation
error messages
Validation attributes allow for defining the error message that is displayed on invalid input:
// {0} is the property name; {2} is MinimumLength and {1} is StringLength:
[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]
use json property names in validation errors
Configure like this. Note that the JsonNamingPolicy is optional:
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(
        new SystemTextJsonValidationMetadataProvider(JsonNamingPolicy.CamelCase));
});
// ...
[Required] Attribute and Non-nullable Reference Types
MVC validates non-nullable properties or parameters as if they had been decorated with [Required(AllowEmptyStrings = false)].
For example, consider this model:
public class Person
{
    public string Name { get; set; }
}
A missing value for Name yields a validation error.
To disable this behavior:
builder.Services.AddControllers(
    options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);
[Required] Validation on the Server
On the server, a required value is considered missing if the property is null. [Required] attribute’s error message is 
However, The value '' is invalid. To customize this message, either make the field nullable, or:
builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(_ => "The field is required.");
    });
builder.Services.AddSingleton
    <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
[Required] Validation on the Client
On the client:
- A value is considered present only if input is entered (including whitespace).
 - Validation handles non-nullable types the same as nullables.
 
So, you get client-side validation even without the [Required] attribute.
[Remote] Attribute
Implements client-side validation that requires calling a method on the server to determine whether field input is valid.
Documentation: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-7.0#remote-attribute
custom attributes
Use custom attributes in scenarios where built-in attributes are insufficient.
Documentation: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-7.0#custom-attributes
Top-Level Node Validation
Model-bound top-level nodes (action parameters; controller properties; page handler parameters; page model properties) are validated in addition to model properties.
Documentation: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-7.0#top-level-node-validation
Maximum Errors, Maximum Recursion
Validation stops after the maximum number of validation errors is reached (default=200). To configure:
builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
    });
// ...
For models that are deep or infinitely recursive, validation may result in a stack overflow. The validation depth (default=32) can be configured:
builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxValidationDepth(128);
    });
// ...
Client-side Validation
Prevents submission until the form is valid. There is a jQuery Unobtrusive Validation script that is a custom Microsoft library built on the jQuery Validation plug-in. Prevents having to code validation logic both server side and client side.
Documentation: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-7.0#client-side-validation