在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初始化时就能读取缓存的内容了,具体操作如下:
- 写一个缓存请求体的中间件
这个中间件要在请求处理的早期执行,读取并缓存请求体,同时保证后续中间件还能正常读取原始流:
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>(); } }
- 在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();
- 修改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
相关产品推荐
相关产品推荐

