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

.NET Core 3.1 IAsyncExceptionFilter中无法读取HttpContext请求Body问题

问题场景

在.NET Core 3.1 Web API项目中实现了继承IAsyncExceptionFilter接口的全局异常过滤器,目标是捕获未处理异常,同时将异常信息与对应的HTTP请求数据一并记录到数据库。
过滤器实现代码如下:

public class ServiceExceptionInterceptor : IAsyncExceptionFilter
{
    public async Task OnExceptionAsync(ExceptionContext context)
    {
        var errorLogService = (IErrorLogService) context.HttpContext.RequestServices.GetService(typeof(IErrorLogService));

        var req = context.HttpContext.Request;

        var method = req.Method;
        var uri = $"{req.Scheme}://{req.Host}/{req.Path}{req.QueryString}";

        string body = null;

        if (req.ContentLength != null)
        {
            var reader = new StreamReader(req.Body);
            // 此处读取到的body始终为空字符串
            body = await reader.ReadToEndAsync();
        }

        var exception = new Models.DB.Exception()
        {
            Type = context.Exception.GetType().ToString(),
            Message = context.Exception.Message,
            StackTrace = context.Exception.StackTrace,
            CreatedAt = DateTime.UtcNow,
            Method = method,
            Uri = uri,
            Body = body
        };

        errorLogService.LogError(exception);

        var error = new Error()
        {
            StatusCode = 500,
            Message = "Something went wrong. There were some technical issues while processing your request."
        };

        context.Result = new JsonResult(error) { StatusCode = StatusCodes.Status500InternalServerError };
    }
}

当前逻辑中通过req.ContentLength != null判断请求体是否存在的逻辑运行正常:无请求体时req.ContentLength为null,携带请求体时能获取到正确长度值,但读取到的请求体内容始终为空字符串。
参考网上方案在Startup中注册中间件调用EnableBuffering()启用请求缓冲后问题仍然存在:

app.Use(async (context, next) => {
    context.Request.EnableBuffering();
    await next();
});

测试发现,只有移除Controller动作方法参数上的[FromBody]特性时,才能正常读取到请求体内容,示例接口代码如下:

[HttpPost]
[Route("api/questions/{questionId}/comments")]
public async Task<ActionResult<Comment>> AddComment(int questionId, /*[FromBody] AddCommentRequestBody requestBody*/) 
// 移除[FromBody]后过滤器可以正常读取请求体
{
    throw new Exception("test exception");

    // 业务逻辑省略

    return Ok(addedComment);
}

核心诉求:在不移除Controller端点[FromBody]特性的前提下,正常读取请求体内容。

问题根因

两个核心问题导致读取失败:

  1. EnableBuffering中间件注册顺序错误:如果该中间件放在MVC/路由/模型绑定相关中间件之后注册,[FromBody]触发的模型绑定逻辑会先读取完未开启缓冲的请求流,后续再开启缓冲没有任何效果。
  2. 读取请求体前未重置流位置:即使成功开启请求缓冲,模型绑定逻辑读完请求体后,Request.Body流的读取指针会停在流的末尾,直接调用ReadToEndAsync()从当前指针位置向后读取,只能得到空字符串。另外默认的StreamReader读取完成后会自动释放底层请求流,会影响后续组件的正常运行。
修复方案

按以下两步调整即可解决问题:

1. 调整缓冲中间件的注册顺序

将启用请求缓冲的中间件放到Configure方法中所有其他中间件的最前面,确保在任何组件读取请求体前就开启缓冲功能:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 该中间件必须放在最前面,优先于所有会读取请求的组件
    app.Use(async (context, next) =>
    {
        context.Request.EnableBuffering();
        // 大文件场景可以传入缓冲阈值,超过阈值的内容会写入磁盘,避免占用过多内存
        // context.Request.EnableBuffering(1024 * 30);
        await next();
    });

    // 其余中间件按原有顺序放在后面
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

2. 修正请求体读取逻辑

读取前先将流指针重置到流的起始位置,同时构造StreamReader时指定leaveOpen: true,避免读取完成后释放底层请求流,读取完成后可按需将指针重置回起始位置,不影响其他组件读取请求体:

string body = null;
if (req.ContentLength != null && req.ContentLength > 0)
{
    // 将流读取指针重置到起始位置
    req.Body.Position = 0;
    // 指定leaveOpen为true,读取完成后不释放底层流
    using (var reader = new StreamReader(
        req.Body, 
        Encoding.UTF8, 
        detectEncodingFromByteOrderMarks: true, 
        bufferSize: 1024, 
        leaveOpen: true))
    {
        body = await reader.ReadToEndAsync();
    }
    // 可选:读取完重置指针位置,避免影响后续逻辑
    req.Body.Position = 0;
}

调整完成后,即使接口参数标记了[FromBody],异常过滤器中也能正常读取到完整的请求体内容。


内容的提问来源于stack exchange,提问作者Mertcan Seğmen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 20:43:10