ModelState is the request-state dictionary used by ASP.NET Core MVC and Razor Pages. It holds the results of model binding and validation, including errors associated with a model or its properties. The framework validates bound data before an action or page handler runs, but a Razor Page handler still runs when validation fails. Check ModelState.IsValid before using the data for business logic. section 16.3.2 section 15.3
Listing 16.7 Checking model state to view the validation result
public class CheckoutModel : PageModel #1
{
[BindProperty] #2
public UserBindingModel Input { get; set; } #2
public IActionResult OnPost() #3
{
if (!ModelState.IsValid) #4
{
return Page(); #5
}
/* Save to the database, update user, return success */ #6
return RedirectToPage("Success");
}
}
In the checkout handler, an invalid ModelState leads to Page(), which redisplays the form with the submitted values and validation messages. Only a valid submission proceeds toward saving and a redirect to the success page. section 16.3.2
Validation is automatic; choosing the response is not. Returning Page() is useful when a Razor Page should let the user correct a form. For an API, a validation failure commonly produces a 400 Bad Request response containing errors. The [ApiController] attribute ordinarily supplies that API behavior, including a Problem Details response. A custom action filter is an alternative when you cannot or do not want to use the attribute. section 16.3.2 section 22.1.3
Listing 22.5 The action filter for validating ModelState
public class ValidateModelAttribute : ActionFilterAttribute #1
{
public override void OnActionExecuting( #2
ActionExecutingContext context) #2
{
if (!context.ModelState.IsValid) #3
{
context.Result = #4
new BadRequestObjectResult(context.ModelState); #4
}
}
}
The custom action filter checks context.ModelState.IsValid before the action executes. If it is false, setting context.Result to a BadRequestObjectResult containing ModelState short-circuits the action. section 22.1.3
Testing the invalid path
A controller unit test does not run MVC model binding, so passing an invalid model alone will not populate ModelState. Add an error with controller.ModelState.AddModelError before calling the action, then check its result. Keep the test model consistent with that error so the test represents a situation that could occur in the running application. section 36.2
ModelState is also a reason to choose an MVC or Razor Pages filter rather than middleware for shared response logic: filters can access MVC constructs such as ModelState, while general middleware cannot. section 21.3


