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

在HttpRequestTelemetryInitializer中读取请求体时遭遇“Cannot access a disposed object”错误的解决求助

在HttpRequestTelemetryInitializer中读取请求体时遭遇“Cannot access a disposed object”错误的解决求助

我最近碰到个头疼的问题:想通过HttpRequestTelemetryInitializer类读取传入请求的Body内容,GetRequestHeader方法跑起来完全没问题,但在GetRequestBody方法里执行var body = reader.ReadToEndAsync().Result;这行时,总是弹出这个错误:

ERROR: Cannot access a disposed object. Object name: 'HttpRequestStream'.

有没有大佬能帮我看看该怎么解决这个问题?

先贴出我的代码片段:

public class HttpRequestTelemetryInitializer : ITelemetryInitializer
{
    private const string _requestBody = "RequestBody";
    private const string _requestHeaders = "RequestHeaders";
    private readonly List<string> _skipBodyLoggingOnErrorApiList;
    private readonly List<string> _skipHeaderLoggingOnErrorApiList;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public HttpRequestTelemetryInitializer(IHttpContextAccessor httpContextAccessor, IConfiguration configuration)
    {
        _httpContextAccessor = httpContextAccessor;
        // 从配置初始化跳过日志的API列表
        _skipBodyLoggingOnErrorApiList = configuration.GetSection("SkipBodyLoggingOnErrorApiList").Get<List<string>>() ?? new List<string>();
        _skipHeaderLoggingOnErrorApiList = configuration.GetSection("SkipHeaderLoggingOnErrorApiList").Get<List<string>>() ?? new List<string>();
    }

    public void Initialize(ITelemetry telemetry)
    {
        var requestTelemetry = telemetry as RequestTelemetry;
        if (requestTelemetry == null || _httpContextAccessor.HttpContext == null)
            return;

        var httpContext = _httpContextAccessor.HttpContext;
        // 获取请求头(这个方法正常工作)
        var headers = GetRequestHeaders(httpContext.Request);
        requestTelemetry.Properties.Add(_requestHeaders, headers);

        // 获取请求体时触发错误
        var body = GetRequestBody(httpContext.Request);
        if (!string.IsNullOrEmpty(body))
        {
            if (!_skipBodyLoggingOnErrorApiList.Any(api => httpContext.Request.Path.StartsWithSegments(api)))
            {
                requestTelemetry.Properties.Add(_requestBody, body);
            }
        }
    }

    private string GetRequestHeaders(HttpRequest request)
    {
        var headers = new StringBuilder();
        foreach (var header in request.Headers)
        {
            headers.AppendLine($"{header.Key}: {string.Join(", ", header.Value)}");
        }
        return headers.ToString();
    }

    private string GetRequestBody(HttpRequest request)
    {
        if (!request.ContentLength.HasValue || request.ContentLength == 0)
            return string.Empty;

        request.Body.Position = 0;
        using var reader = new StreamReader(request.Body);
        // 报错的核心代码行
        var body = reader.ReadToEndAsync().Result;
        request.Body.Position = 0;
        return body;
    }
}

问题根源

这个错误本质是请求流已经被ASP.NET Core的中间件管道提前销毁了。当ITelemetryInitializer的Initialize方法执行时,请求的处理流程大概率已经走完,原始的HttpRequestStream已经被释放,这时候再去读取自然会报错。另外,你用.Result同步等待异步方法的写法也有风险,容易引发死锁问题。

解决步骤

咱们得在请求流被释放之前就把它缓存起来,这样后续Telemetry初始化时就能读取缓存的内容了,具体操作如下:

  1. 写一个缓存请求体的中间件
    这个中间件要在请求处理的早期执行,读取并缓存请求体,同时保证后续中间件还能正常读取原始流:
public class RequestBodyCacheMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context)
    {
        // 开启请求流可重复读取的功能
        context.Request.EnableBuffering();

        // 读取请求体并缓存到HttpContext的Items集合中
        if (context.Request.ContentLength.HasValue && context.Request.ContentLength > 0)
        {
            using var reader = new StreamReader(context.Request.Body, leaveOpen: true);
            var body = await reader.ReadToEndAsync();
            context.Request.Body.Position = 0;
            context.Items["CachedRequestBody"] = body;
        }

        // 把请求传递给下一个中间件
        await _next(context);
    }
}

// 扩展方法方便注册中间件
public static class RequestBodyCacheMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestBodyCache(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestBodyCacheMiddleware>();
    }
}
  1. 在Program.cs中注册这个中间件
    一定要把它放在最靠前的位置,确保在其他中间件读取请求流之前完成缓存:
var builder = WebApplication.CreateBuilder(args);

// ... 其他服务注册代码

builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<ITelemetryInitializer, HttpRequestTelemetryInitializer>();

var app = builder.Build();

// 注册请求体缓存中间件,放在所有业务中间件之前
app.UseRequestBodyCache();

// ... 其他中间件注册(比如UseRouting、UseAuthorization等)

app.Run();
  1. 修改HttpRequestTelemetryInitializer的GetRequestBody方法
    现在直接从HttpContext的缓存里拿内容,不用再碰原始请求流了:
private string GetRequestBody(HttpRequest request)
{
    if (request.HttpContext.Items.TryGetValue("CachedRequestBody", out var cachedBody) && cachedBody is string body)
    {
        return body;
    }
    return string.Empty;
}

额外提醒

  • 如果你的业务里有大体积请求(比如文件上传),建议不要缓存整个请求体,避免占用过多内存,可以加个大小判断,只缓存小体积的请求内容。
  • 尽量避免用.Result同步等待异步方法,后续如果有其他异步操作,优先用await。

备注:内容来源于stack exchange,提问作者Mehmet Serkan Ekinci

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.16 06:54:34