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

添加请求日志中间件后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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 12:28:26