如何捕获.NET 6 Web API的One or more validation errors occurred错误
问题解决方案
完全可以实现,你当前无法捕获这类错误的核心原因有两个:
- 这类JSON反序列化错误属于
[ApiController]特性内置的自动模型验证逻辑触发的400响应,框架会在Action执行前直接短路返回结果,不会抛出未处理异常,普通的异常捕获中间件无法感知 - 你当前的自定义日志中间件位置偏后,且默认没有开启响应流缓冲,无法读取下游中间件生成的响应内容
推荐实现方案(性能最优,无侵入)
直接在服务配置阶段注入模型验证失败的回调逻辑,不需要修改现有中间件管道,只会在模型验证/JSON解析真正失败时触发,不会对正常请求增加额外开销:
- 在
ConfigureServices方法中修改控制器注册配置,自定义无效模型状态的响应工厂:
services.AddControllers() .ConfigureApiBehaviorOptions(options => { options.InvalidModelStateResponseFactory = actionContext => { // 从DI容器获取日志实例 var logger = actionContext.HttpContext.RequestServices .GetRequiredService<ILogger<Program>>(); // 提取所有验证错误,包括JSON反序列化错误 var errorDetails = actionContext.ModelState .Where(stateEntry => stateEntry.Value.Errors.Any()) .ToDictionary( keySelector: entry => entry.Key, elementSelector: entry => entry.Value.Errors.Select(err => err.ErrorMessage).ToArray() ); // 写入日志,可按需补充请求体、请求头、用户标识等上下文 logger.LogWarning( "接口请求参数绑定失败,路径:{RequestPath},错误详情:{@ErrorDetails}", actionContext.HttpContext.Request.Path, errorDetails ); // 返回和默认格式完全一致的400响应,不影响现有客户端逻辑 var problemDetail = new ValidationProblemDetails(errorDetails) { Status = StatusCodes.Status400BadRequest }; return new BadRequestObjectResult(problemDetail); }; });
所有你遇到的JSON类型转换、字段缺失、格式不合法等参数解析错误,都会被这个逻辑捕获,日志里可以拿到完整的错误字段、错误原因、请求路径信息。
备选方案:升级现有日志中间件实现全局响应拦截
如果你需要在日志中间件里统一拦截所有响应(包括非MVC中间件生成的400错误),按以下方式调整即可:
- 调整中间件顺序,把你的自定义日志中间件放到所有业务中间件之前,保证能覆盖所有请求流:
app.UseDeveloperExceptionPage(); // 日志中间件前置,放在所有其他业务中间件前面 app.UseMiddleware<MyCustomLoggingMiddleware>(); app.UseCors(); app.UseSession(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
- 修改中间件逻辑,开启响应流缓冲,解决默认响应流只能单向读取、读完无法返回给客户端的问题:
public class MyCustomLoggingMiddleware { private readonly RequestDelegate _next; private readonly ILogger<MyCustomLoggingMiddleware> _logger; public MyCustomLoggingMiddleware(RequestDelegate next, ILogger<MyCustomLoggingMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext context) { // 暂存原始响应流,替换为可读写的内存流 var originalResponse = context.Response.Body; using var bufferedResponse = new MemoryStream(); context.Response.Body = bufferedResponse; try { await _next(context); // 仅处理400响应,减少不必要的性能开销 if (context.Response.StatusCode == StatusCodes.Status400BadRequest) { bufferedResponse.Position = 0; var responseContent = await new StreamReader(bufferedResponse).ReadToEndAsync(); // 识别模型验证/JSON解析错误 if (responseContent.Contains("JSON value could not be converted")) { _logger.LogWarning( "请求JSON解析失败,路径:{Path},响应内容:{Content}", context.Request.Path, responseContent ); } // 重置流位置,准备写回原始响应 bufferedResponse.Position = 0; } } finally { // 将缓冲的响应内容写回原始流,保证客户端正常接收响应 await bufferedResponse.CopyToAsync(originalResponse); context.Response.Body = originalResponse; } } }
注意事项
- 生产环境请移除
UseDeveloperExceptionPage(),替换为正式的异常处理中间件,避免泄露堆栈等敏感信息 - 如果需要记录触发错误的原始请求体,可以在中间件/回调逻辑中开启请求流缓冲,读取
context.Request.Body的内容即可
内容的提问来源于stack exchange,提问作者Slef Ved
相关产品推荐
相关产品推荐

