.Net Core中间件记录Bad Request时读取请求体为空怎么解决
问题根因
- 核心问题是请求体缓冲开启时机错误:你当前在
await _next(context)执行完成后才调用context.Request.EnableBuffering(),此时ASP.NET Core内置的模型绑定、验证中间件已经读取过请求体,默认配置下请求体读取后不会保留内容,流指针处于末尾,因此读取结果为空。 - 次要问题:要提取400响应的错误列表,需要提前对响应体做缓存处理,默认响应体是只进流,发送到客户端后无法读取内容。
修复实现
调整中间件逻辑
修改后的完整中间件逻辑如下,包含提前开启缓冲、读写请求响应内容、解析字段调用日志的完整流程:
using InfiniteDocumentsAPI.Core.Services; using Microsoft.AspNetCore.Http; using System; using System.IO; using System.Text; using System.Text.Json; using System.Threading.Tasks; namespace MBFSPT_OCRABBYY_InfiniteDocumentsAPI.Middlewares { public class RequestLoggingMiddleware { private readonly RequestDelegate _next; public RequestLoggingMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context, LoggerService log) { // 提前开启请求体缓冲,允许重复读取 context.Request.EnableBuffering(); // 缓存原始响应流,替换为可重读的内存流 var originalResponseBody = context.Response.Body; using var responseMemoryStream = new MemoryStream(); context.Response.Body = responseMemoryStream; try { await _next(context); } finally { if (context.Response.StatusCode == 400) { string requestStr = string.Empty; string responseStr = string.Empty; try { // 读取请求体 context.Request.Body.Position = 0; using (var reader = new StreamReader( context.Request.Body, encoding: Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true)) { requestStr = await reader.ReadToEndAsync(); } context.Request.Body.Position = 0; // 读取响应体 responseMemoryStream.Position = 0; using (var reader = new StreamReader( responseMemoryStream, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true)) { responseStr = await reader.ReadToEndAsync(); } // 解析字段调用日志 if (!string.IsNullOrEmpty(requestStr) && !string.IsNullOrEmpty(responseStr)) { using var requestDoc = JsonDocument.Parse(requestStr); var appInfo = requestDoc.RootElement.GetProperty("applicationInfo"); string applicationId = appInfo.GetProperty("applicationId").GetInt32().ToString(); string applicationNumber = appInfo.GetProperty("applicationNumber").GetString() ?? string.Empty; string applicationStatusLocalText = appInfo.GetProperty("applicationStatusLocalText").GetString() ?? string.Empty; using var responseDoc = JsonDocument.Parse(responseStr); var errors = responseDoc.RootElement.GetProperty("errors"); string stringfiedResponsesErrorList = errors.GetRawText(); log.Trace($"BatchNotCreated{stringfiedResponsesErrorList}", applicationId, applicationNumber, 0, applicationStatusLocalText); } } catch { // 可自行添加解析异常的兜底处理逻辑 } finally { // 将响应内容写回原始流,保证客户端正常收到响应 responseMemoryStream.Position = 0; await responseMemoryStream.CopyToAsync(originalResponseBody); context.Response.Body = originalResponseBody; } } else { // 非400状态码直接返回响应 responseMemoryStream.Position = 0; await responseMemoryStream.CopyToAsync(originalResponseBody); context.Response.Body = originalResponseBody; } } } } }
注意事项
- 确保中间件注册顺序在路由、认证、模型验证相关中间件之前,在
Program.cs中app.UseMiddleware<RequestLoggingMiddleware>()的调用位置需放在app.MapControllers()之前。 - 如果项目使用Newtonsoft.Json作为序列化组件,将代码中
JsonDocument相关逻辑替换为JObject.Parse即可,解析逻辑保持一致。
内容的提问来源于stack exchange,提问作者Cagri Tacyildiz
相关产品推荐
相关产品推荐

