Hellang.Middleware.ProblemDetails验证错误状态码映射疑问及配置咨询
Great question—let's unpack what's happening here, why the status code shifts from 400 to 422, and how you can adjust it to fit your project's needs.
Why 422 instead of 400?
First, let's clarify the HTTP status code semantics:
- 400 Bad Request: A generic code for any request the server can't understand (malformed syntax, invalid URL structure, etc.).
- 422 Unprocessable Entity: A more specific code (originally defined for WebDAV, now widely adopted in REST APIs) that means: the request syntax is valid, but the server can't process the content due to validation errors.
The AddProblemDetailsConventions method aligns your API's error responses with the RFC 7870 Problem Details for HTTP APIs specification, which emphasizes using precise status codes to communicate clear error context. Using 422 for validation errors is considered a best practice because it tells clients immediately that their request was structurally correct, but failed business/field validation—unlike the vague 400.
What does AddProblemDetailsConventions actually change?
This method applies pre-built conventions to standardize error responses beyond just validation errors. Some key mappings include:
- Mapping MVC ModelState validation failures (and
ValidationException) to 422 instead of 400 - Mapping
NotFoundExceptionto 404 with consistent Problem Details fields - Mapping
ConflictExceptionto 409 - Ensuring uniform
title,detail, andtypefields across all error responses to match RFC 7870 guidelines
When you comment out this line, your API falls back to the default ApiController behavior, which treats all validation errors as a generic 400 Bad Request.
How to configure validation errors to return 400 again?
If your test suite expects 400 for validation scenarios, you can override the convention by customizing the Problem Details middleware. Here are two practical approaches:
1. Customize Problem Details at runtime
Use the CustomizeProblemDetails option to adjust the status code specifically for ModelState validation failures:
builder.Services.AddProblemDetails(options => { options.CustomizeProblemDetails = context => { // Check if this is a ModelState validation error mapped to 422 var modelStateFeature = context.HttpContext.Features.Get<ModelStateFeature>(); if (context.ProblemDetails.Status == StatusCodes.Status422UnprocessableEntity && modelStateFeature?.ModelState != null && !modelStateFeature.ModelState.IsValid) { // Override status code and title to match 400 behavior context.ProblemDetails.Status = StatusCodes.Status400BadRequest; context.ProblemDetails.Title = "One or more validation errors occurred."; } }; }); // Keep AddProblemDetailsConventions if you want to retain other convention benefits builder.Services.AddProblemDetailsConventions();
2. Map ValidationException directly to 400
If you want to handle all ValidationException instances (not just MVC ModelState errors) to return 400:
builder.Services.AddProblemDetails(options => { options.Map<ValidationException>(ex => { var problemDetails = new ProblemDetails { Status = StatusCodes.Status400BadRequest, Title = "One or more validation errors occurred.", Detail = ex.Message }; // Add validation errors to the response (matches default ApiController format) if (ex.Errors.Any()) { problemDetails.Extensions["errors"] = ex.Errors .GroupBy(e => e.PropertyName) .ToDictionary( group => group.Key, group => group.Select(e => e.ErrorMessage).ToArray() ); } return problemDetails; }); });
Either approach lets you keep the consistent Problem Details format while forcing validation errors to return 400 instead of 422.
内容的提问来源于stack exchange,提问作者Katia

