liveBook · Manning

ModelState

As explained in Pro ASP.NET Core 7, Tenth Edition

ModelStateDictionary is the ASP.NET Core MVC collection that stores bound model values and validation errors for request fields and properties.

ModelState is the ControllerBase property that exposes a ModelStateDictionary for the current request. It tracks the outcome of model binding, especially validation errors for model properties. Check ModelState.IsValid before using a bound object: binding can produce an object even when request values are missing or invalid. section 29.3 section 9.3.5

Table 29.3 Selected ModelStateDictionary members (view table figure)

Name

Description

AddModelError(property, message)

This method is used to record a model validation error for the specified property.

GetValidationState(property)

This method is used to determine whether there are model validation errors for a specific property, expressed as a value from the ModelValidationState enumeration.

IsValid

This property returns true if all the model properties are valid and returns false otherwise.

Clear()

This property clears the validation state.

The member table shows the working API: IsValid checks the overall result, AddModelError records a property error, GetValidationState checks one property, and Clear resets validation state. section 29.3

Accept a valid form; redisplay an invalid one

In a POST action receiving a GuestResponse, check ModelState.IsValid. Save the response and render Thanks only when it is true; otherwise return View() to redisplay the form. Razor validation tag helpers can then use the errors in the view context to show a summary and mark invalid inputs. A nullable bool? for WillAttend matters here: unlike bool, its null value lets [Required] detect that the guest made no selection. section 3.2.9

Validation attributes are not the only source of errors. In an order checkout action, an empty cart triggers ModelState.AddModelError("", "Sorry, your cart is empty!"); IsValid then prevents the order from being saved. For a property-specific rule, first check GetValidationState(nameof(Product.Price)) == ModelValidationState.Valid. Only then test whether the price is positive and, if necessary, add an error under that property name. This avoids piling a price-rule message onto a value that could not be bound as a decimal. section 9.3.5 section 29.3.3

Listing 29.9 Explicit validation in the FormController.cs file in the Controllers folder
using Microsoft.AspNetCore.Mvc;
using WebApp.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace WebApp.Controllers {

    [AutoValidateAntiforgeryToken]
    public class FormController : Controller {
        private DataContext context;

        public FormController(DataContext dbContext) {
            context = dbContext;
        }
 
        public async Task<IActionResult> Index(long? id) {
            return View("Form", await context.Products
                .OrderBy(p => p.ProductId)
                .FirstOrDefaultAsync(p => id == null 
                    || p.ProductId == id));
        }
 
        [HttpPost]
        public IActionResult SubmitForm(Product product) {
                
            if (ModelState.GetValidationState(nameof(Product.Price))
                    == ModelValidationState.Valid && product.Price <= 0) {
                ModelState.AddModelError(nameof(Product.Price),
                    "Enter a positive price");
            }
                        
            if (ModelState.GetValidationState(nameof(Product.CategoryId))
                == ModelValidationState.Valid && 
                        !context.Categories.Any(c =>
                            c.CategoryId == product.CategoryId)) {
                ModelState.AddModelError(nameof(Product.CategoryId),
                    "Enter an existing category ID");
            }
                        
            if (ModelState.GetValidationState(nameof(Product.SupplierId))
                    == ModelValidationState.Valid && 
                        !context.Suppliers.Any(s =>
                            s.SupplierId == product.SupplierId)) {
                ModelState.AddModelError(nameof(Product.SupplierId),
                    "Enter an existing supplier ID");
            }
                        
            if (ModelState.IsValid) {
                TempData["name"] = product.Name;
                TempData["price"] = product.Price.ToString();
                TempData["categoryId"] = product.CategoryId.ToString();
                TempData["supplierId"] = product.SupplierId.ToString();
                return RedirectToAction(nameof(Results));
            } else {
                return View("Form");
            }
        }
 
        public IActionResult Results() {
            return View(TempData);
        }
    }
}

The controller listing puts explicit property checks before the final ModelState.IsValid decision, including checks against database IDs. section 29.3.3

A web API can return BadRequest(ModelState) when validation fails, instead of redisplaying a view; the response describes errors to the client, though clients may rely only on its 400 status. Controller unit tests can add an error directly to ModelState to exercise the invalid branch, then verify that no order was saved. section 19.5.4 section 9.3.5

How Pro ASP.NET Core 7, Tenth Edition covers it

How deep it goes
Defines ModelStateDictionary and its principal members, then works through implicit checks, explicit property rules, and controller tests.
Examples
Uses an RSVP form, empty-cart checkout tests, positive-price checks, and a product API returning validation errors.
What it assumes
Assumes familiarity with C# controller actions and models while defining the dictionary and demonstrating how binding affects validation.
What you can do after
Apply it via ASP.NET Core MVC to reject invalid submissions, add rule-specific errors, and return errors through views or API responses.
How it connects
Connects model binding and validation attributes to Razor validation tag helpers, ModelValidationState, and BadRequest responses.

Also explained in

Compare how 3 books explain ModelState →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Pro ASP.NET Core 7, Tenth Edition. The section links point to the parts of the book it draws on.