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

.NET Core 3.1异常过滤器中如何读取请求内容?

在.NET Core 3.1中获取异常过滤器里的原始请求内容

问题核心在于:Request.Body是单向只读流,控制器绑定模型时已将流读取至末尾,因此异常过滤器中再读取会得到空内容。要解决这个问题,需提前缓存请求体,让流支持重复读取。

实现步骤

1. 编写请求体缓存中间件

创建中间件在控制器处理请求前读取并缓存请求体,同时重置流位置确保后续流程正常读取:

public class RequestBodyLoggingMiddleware
{
    private readonly RequestDelegate _next;

    public RequestBodyLoggingMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        // 仅处理包含请求体的HTTP方法(POST/PUT/PATCH等)
        if (!HttpMethods.IsGet(context.Request.Method) && context.Request.ContentLength > 0)
        {
            try
            {
                // 启用请求体缓冲,支持多次读取
                // 设置1MB内存缓冲阈值,超过则自动写入磁盘避免内存溢出
                context.Request.EnableBuffering(bufferThreshold: 1024 * 1024);

                // 读取请求体内容
                using var reader = new StreamReader(context.Request.Body, leaveOpen: true);
                var requestBody = await reader.ReadToEndAsync();

                // 重置流位置,保证后续中间件/控制器能正常读取
                context.Request.Body.Position = 0;

                // 将缓存内容存入HttpContext.Items,供过滤器获取
                context.Items["CachedRequestBody"] = requestBody;
            }
            catch (Exception ex)
            {
                // 记录读取失败异常,不阻断正常请求流程
                var logger = context.RequestServices.GetRequiredService<ILogger<RequestBodyLoggingMiddleware>>();
                logger.LogWarning(ex, "Failed to read request body for logging");
            }
        }

        await _next(context);
    }
}

// 扩展方法简化中间件注册
public static class RequestBodyLoggingMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestBodyCaching(this IApplicationBuilder app)
    {
        return app.UseMiddleware<RequestBodyLoggingMiddleware>();
    }
}

2. 注册中间件

在Startup.cs的Configure方法中注册中间件,需放在控制器处理流程之前(如UseRouting之后、UseEndpoints之前):

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...其他中间件(异常处理、静态文件等)

    app.UseRequestBodyCaching();

    app.UseRouting();

    // ...认证、授权中间件

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

3. 修改异常过滤器获取缓存内容

在自定义异常过滤器中,从HttpContext.Items取出缓存的请求体即可:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
    private readonly ILogger<CustomExceptionFilterAttribute> _logger;

    public CustomExceptionFilterAttribute(ILogger<CustomExceptionFilterAttribute> logger)
    {
        _logger = logger;
    }

    public override void OnException(ExceptionContext context)
    {
        var request = context.HttpContext.Request;
        var requestBody = context.HttpContext.Items["CachedRequestBody"] as string ?? string.Empty;

        // 构造日志信息
        var logDetails = new
        {
            RequestMethod = request.Method,
            RequestPath = request.Path,
            RequestQuery = request.QueryString.ToString(),
            RequestBody = requestBody,
            ExceptionType = context.Exception.GetType().Name,
            ExceptionMessage = context.Exception.Message,
            StackTrace = context.Exception.StackTrace
        };

        _logger.LogCritical(JsonSerializer.Serialize(logDetails));

        // 如需返回自定义错误响应,可在此设置context.Result
        // context.Result = new JsonResult(new { error = "An unexpected error occurred" }) { StatusCode = StatusCodes.Status500InternalServerError };
        // context.ExceptionHandled = true;

        base.OnException(context);
    }
}

注意事项

  • 大请求处理:通过bufferThreshold参数限制内存缓冲上限,超过阈值的请求体自动写入磁盘,避免内存溢出。
  • 编码兼容:若请求使用非UTF-8编码,需在StreamReader构造时指定对应编码(如Encoding.GetEncoding("GB2312"))。
  • GET请求跳过:GET请求通常无请求体,无需处理以减少性能开销。
  • 异常防护:中间件中添加try-catch,避免读取请求体的异常影响正常业务流程。

内容的提问来源于stack exchange,提问作者Anders Finn Jørgensen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 22:11:02