liveBook · Manning

Manual model validation

As explained in Building Web APIs with ASP.NET Core

Manual ModelState validation is explicit application logic that inspects model-binding and validation results and selects an HTTP response.

Manual model validation is explicit controller logic that inspects ModelState after model binding and validation, then chooses the HTTP response. It becomes necessary when automatic [ApiController] validation is disabled, because actions still execute with invalid input. section 6.2.3

A practical implementation follows this sequence:

  • Keep [ApiController] enabled, but scope the exception to the action that needs custom behavior instead of suppressing automatic validation globally. section 6.2.3
  • Apply a ManualValidationFilter attribute to the selected action; the filter removes ModelStateInvalidFilter from that action's filter collection. section 6.2.3
  • Check ModelState.IsValid inside the action, construct ValidationProblemDetails when validation fails, and return the response appropriate to the invalid input. section 6.2.3 section 6.3.4
  • Process the request normally when ModelState is valid. section 6.3.4

Board-game API validation

A DomainsController action can use [ManualValidationFilter] and inspect ModelState itself. For an invalid model, it can return HTTP 403 when Id is not 3 and Name is not "Wargames", or HTTP 400 for other validation failures. A valid model continues through the normal action path. section 6.3.4

Listing 6.7 ManualValidationFilterAttribute
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Infrastructure;

namespace MyBGList.Attributes
{
   public class ManualValidationFilterAttribute 
       : Attribute, IActionModelConvention
   {
       public void Apply(ActionModel action)
       {
           for (var i = 0; i < action.Filters.Count; i++)
           {
               if (action.Filters[i] is ModelStateInvalidFilter
                   || action.Filters[i].GetType().Name == 
                       "ModelStateInvalidFilterFactory")
               {
                   action.Filters.RemoveAt(i);
                   break;
               }
           }
       }
   }
}

Inspect the filter implementation that removes the automatic ModelStateInvalidFilter through IActionModelConvention. section 6.2.3

[HttpGet(Name = "GetDomains")]
[ResponseCache(Location = ResponseCacheLocation.Any, Duration = 60)]
public async Task<ActionResult<RestDTO<Domain[]>>> Get(                #1
    [FromQuery] RequestDTO<DomainDTO> input)
{
    if (!ModelState.IsValid)                                           #2
    {
        var details = new ValidationProblemDetails(ModelState);
        details.Extensions["traceId"] = 
            System.Diagnostics.Activity.Current?.Id 
              ?? HttpContext.TraceIdentifier;
        if (ModelState.Keys.Any(k => k == "PageSize"))
        {
            details.Type = 
                "https://tools.ietf.org/html/rfc7231#section-6.6.2";
            details.Status = StatusCodes.Status501NotImplemented;
            return new ObjectResult(details) {
                StatusCode = StatusCodes.Status501NotImplemented
            };
        }
        else
        {
            details.Type = 
                "https://tools.ietf.org/html/rfc7231#section-6.5.1";
            details.Status = StatusCodes.Status400BadRequest;
            return new BadRequestObjectResult(details);
        }
    }
 
    // ... code omitted ...                                            #3
}

Inspect the action-level ModelState check, ValidationProblemDetails construction, trace identifier, and conditional response handling. section 6.2.3

How Building Web APIs with ASP.NET Core covers it

How deep it goes
Explains filter scoping, ModelState inspection, problem-details construction, and conditional status codes, but does not detail validation-rule implementation.
Examples
Uses BoardGamesController invalid query values and a DomainsController POST with domain-specific 403 and 400 responses.
What it assumes
Assumes familiarity with ASP.NET Core controllers, [ApiController], action methods, ModelState, and HTTP response results.
What you can do after
Build custom endpoint responses by disabling automatic validation narrowly, inspecting ModelState, and returning status codes for each failure category.
How it connects
Connects model binding, ModelState, ApiController, action filters, validation problem details, and HTTP status codes.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Building Web APIs with ASP.NET Core. The section links point to the parts of the book it draws on.