You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何捕获.NET 6 Web API的One or more validation errors occurred错误

问题解决方案

完全可以实现,你当前无法捕获这类错误的核心原因有两个:

  • 这类JSON反序列化错误属于[ApiController]特性内置的自动模型验证逻辑触发的400响应,框架会在Action执行前直接短路返回结果,不会抛出未处理异常,普通的异常捕获中间件无法感知
  • 你当前的自定义日志中间件位置偏后,且默认没有开启响应流缓冲,无法读取下游中间件生成的响应内容

推荐实现方案(性能最优,无侵入)

直接在服务配置阶段注入模型验证失败的回调逻辑,不需要修改现有中间件管道,只会在模型验证/JSON解析真正失败时触发,不会对正常请求增加额外开销:

  1. 在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错误),按以下方式调整即可:

  1. 调整中间件顺序,把你的自定义日志中间件放到所有业务中间件之前,保证能覆盖所有请求流:
app.UseDeveloperExceptionPage();
// 日志中间件前置,放在所有其他业务中间件前面
app.UseMiddleware<MyCustomLoggingMiddleware>();
app.UseCors();
app.UseSession();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
});
  1. 修改中间件逻辑,开启响应流缓冲,解决默认响应流只能单向读取、读完无法返回给客户端的问题:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 16:54:22