Validation
Redirect back with validation errors for Minimal APIs, MVC and FastEndpoints, and add Precognition.
Inertia forms post, expect a redirect, and read validation errors from props.errors on the next page. Inertia.Net performs the redirect and carries the errors across it.
- A field maps to its first message. With
o.WithAllErrors = trueit maps to an array of all messages (the client'sform.withAllErrors()expects that). - Error keys are the JSON names of the model members, one naming-policy conversion per segment:
Address.Streetbecomesaddress.street. - Error bags. The client sends
X-Inertia-Error-Bag: createUserwhen the form haserrorBag="createUser". Errors arrive aserrors: { createUser: { email: "..." } }. Without the header,errorsis the flat object{ email: "..." }. A bag stored under a name other thandefaultis always nested by bag name.
Minimal API
WithInertiaValidation() for a route or a route group. It requires services.AddValidation() for attribute validation.
For an Inertia request that is not a GET, a failure becomes a redirect back (303 for PUT/PATCH/DELETE) with the errors. Every other client still gets the normal 400 validation problem. A handler that returns TypedResults.ValidationProblem(...), Results.ValidationProblem(...) or an HttpValidationProblemDetails is converted the same way.
Why not rely on AddValidation alone?
The built-in .NET 10 validation filter is added automatically and always runs outermost: it writes the 400 itself before any filter you add can see it. WithInertiaValidation() disables it on the endpoint and runs the same source-generated validation itself. Do not combine it with your own .DisableValidation().
To report errors yourself, without attribute validation:
Back().WithErrors(IDictionary<string, string[]> errors, string bag = "default") also takes arrays of messages.
MVC
AddInertiaMvc() registers a global filter. An Inertia non-GET request with an invalid ModelState is redirected back with the errors before the action runs, in the bag from X-Inertia-Error-Bag (default default). [ApiController] controllers work too: their automatic 400 is replaced by the same redirect for Inertia requests.
Binding failures get the generic message The value is invalid., because their exception messages can expose internals.
FastEndpoints
c.UseInertia() adds a global pre-processor. A validation failure of an Inertia non-GET request becomes a redirect back with the errors; other requests keep FastEndpoints' 400 JSON. FluentValidation validators work as usual.
c.UseInertia() sets c.Endpoints.Configurator. Pass your own configuration as its argument instead of setting the configurator separately. The pre-processor runs after an endpoint's own pre-processors, so one that already answered (a 403, say) wins.
Precognition (live validation)
Precognition validates a form as the user types, without running the handler. A request with Precognition: true is validated and answered with:
204andPrecognition-Success: truewhen valid;422and{ "message": "...", "errors": { "field": ["..."] } }when not, limited to the fields inPrecognition-Validate-Only;Precognition: trueandVary: Precognitionon every response.
WithInertiaPrecognition() is a superset of WithInertiaValidation(): normal Inertia validation redirects keep working. Any endpoint filter you add after it does not run for a precognitive request. In MVC, Precognition is not available.
Client (Inertia v3 has it built in):
Error bags in the client
All errors for a field
With the option on, errors.email is an array of every message instead of the first.
State and redirects
Validation errors travel in the state store across the redirect. By default that is an encrypted cookie; it can be a session or MVC TempData instead. See Flash data for the store choices and size limits.