添加请求日志中间件后ModelState验证间歇性失败求助
问题根因
你的请求日志中间件在读取请求体后没有重置流的位置,导致后续的模型绑定器无法读取到请求数据。当中间件读取完请求体后,流的指针会停在末尾,此时模型绑定器尝试读取时会得到空内容,进而触发ModelState验证失败。这种间歇性问题的核心原因是:部分请求被ExcludeLogging逻辑跳过日志记录(此时未读取请求体,模型绑定正常),而另一部分请求触发了日志读取逻辑(未重置流位置导致绑定失败)。
解决方案
需要对中间件做两处关键修正:
1. 读取请求体后重置流位置
在读取请求体并完成日志记录后,必须将请求流的Position重置为0,确保模型绑定器能从头读取数据。如果使用ASP.NET Core 3.0+版本,建议用EnableBuffering()替代已过时的EnableRewind()。
2. 正确处理响应体的捕获与还原
当前代码仅创建了响应体的内存流,但未将其替换为上下文的响应流,也没有在处理完成后将内存流内容复制回原始响应流。这会导致响应无法正常返回给客户端,需要补充这部分逻辑。
修正后的中间件代码
public async Task Invoke(HttpContext httpContext, IRequestLogBuilder requestLogBuilder) { var requestPath = httpContext.Request.Path.Value; var request = httpContext.Request; if (requestPath != null && ExcludeLogging(requestPath)) { await _next(httpContext); return; } // 处理请求体:启用缓冲并读取后重置位置 request.EnableBuffering(); // 替代EnableRewind(),适用于ASP.NET Core 3.0+ string requestBody = null; if (request.ContentLength > 0 || !request.ContentLength.HasValue) { // 读取请求体内容,leaveOpen设为true避免关闭流 using (var reader = new StreamReader(request.Body, Encoding.UTF8, leaveOpen: true)) { requestBody = await reader.ReadToEndAsync(); // 重置流位置到开头,确保模型绑定器能读取 request.Body.Position = 0; } // 执行请求日志记录逻辑 // requestLogBuilder.LogRequestDetails(requestBody); } // 处理响应体捕获与还原 var originalResponseBody = httpContext.Response.Body; using (var responseBody = new MemoryStream()) { try { httpContext.Response.Body = responseBody; await _next(httpContext); // 读取响应体用于日志 responseBody.Position = 0; string responseContent = await new StreamReader(responseBody, Encoding.UTF8).ReadToEndAsync(); // 执行响应日志记录逻辑 // requestLogBuilder.LogResponseDetails(responseContent); // 将响应内容复制回原始流,确保客户端能收到响应 responseBody.Position = 0; await responseBody.CopyToAsync(originalResponseBody); } catch (Exception ex) { // 自定义错误处理逻辑 httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; await httpContext.Response.WriteAsync("服务端处理出错"); } finally { httpContext.Response.Body = originalResponseBody; } } }
额外注意事项
- 确保
StreamReader的leaveOpen参数设为true,避免读取请求体后关闭流导致后续无法访问。 - 对于大体积请求,可通过配置
HttpContext.Features.Get<IHttpMaxRequestBodySizeFeature>().MaxRequestBodySize调整缓冲上限,避免内存溢出。 - 测试时可在模型绑定失败的请求中检查日志,确认是否是中间件读取请求体后未重置位置导致的。
内容的提问来源于stack exchange,提问作者Tarak Venkat
相关产品推荐
相关产品推荐

