.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
相关产品推荐
相关产品推荐

