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

.NET 7读取HTTP响应体时遇System.NotSupportedException的排查与解决

异常成因及解决方案

异常原因

System.NotSupportedException的核心原因是:默认的HttpResponse.Body是一个仅支持写入、不支持读取/查找操作的流。当你调用await _next(context)后,后端处理逻辑已经将响应内容写入这个流并开始向客户端发送,此时尝试对原流执行CopyToAsync或Seek操作,会触发流不支持该操作的异常。

正确实现方式

要记录响应体,必须在执行后续中间件/处理程序之前,将原响应体替换为一个可读写的MemoryStream,让所有响应内容先写入这个内存流,之后我们可以安全地读取它,再将内容回写到原流发送给客户端。

修改后的完整中间件代码如下:

using System.Diagnostics;
using System.Text;

namespace WebApi.Middlewares;

public class RequestLoggerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggerMiddleware> _logger;

    public RequestLoggerMiddleware(RequestDelegate next, ILogger<RequestLoggerMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task Invoke(HttpContext context)
    {
        var sw = Stopwatch.StartNew();
        string requestBodyText = string.Empty;

        // 处理请求体记录
        if (context.Request.ContentLength > 0 && context.Request.ContentType?.Contains("application/json") == true)
        {
            var requestBodyStream = new MemoryStream();
            var originalRequestBody = context.Request.Body;
            
            try
            {
                await context.Request.Body.CopyToAsync(requestBodyStream);
                requestBodyStream.Seek(0, SeekOrigin.Begin);
                requestBodyText = await new StreamReader(requestBodyStream, Encoding.UTF8).ReadToEndAsync();
                requestBodyStream.Seek(0, SeekOrigin.Begin);
                context.Request.Body = requestBodyStream; // 重置请求体流,确保后续中间件能读取
            }
            finally
            {
                // 此处无需恢复原流,已将MemoryStream重置,后续中间件可正常读取
            }
        }

        _logger.LogInformation("Incoming request {Method} {Path} {Query} {RequestBody}", 
            context.Request.Method, context.Request.Path, context.Request.QueryString, requestBodyText);

        // 处理响应体记录
        var originalResponseBody = context.Response.Body;
        using var responseBodyStream = new MemoryStream();
        context.Response.Body = responseBodyStream;

        try
        {
            await _next(context); // 执行后续处理,响应会写入responseBodyStream

            sw.Stop();
            var responseTime = sw.Elapsed.TotalMilliseconds;

            // 重置内存流位置,读取响应内容
            responseBodyStream.Seek(0, SeekOrigin.Begin);
            var responseBodyText = await new StreamReader(responseBodyStream, Encoding.UTF8).ReadToEndAsync();

            _logger.LogInformation("Outgoing response {StatusCode} {Method} {Path} {Query} {ResponseBody} {ResponseTime:0.000}ms",
                context.Response.StatusCode,
                context.Request.Method,
                context.Request.Path,
                context.Request.QueryString,
                responseBodyText,
                responseTime);

            // 将响应内容复制回原流,发送给客户端
            responseBodyStream.Seek(0, SeekOrigin.Begin);
            await responseBodyStream.CopyToAsync(originalResponseBody);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error processing request {Method} {Path} {Query}", 
                context.Request.Method, context.Request.Path, context.Request.QueryString);
            throw;
        }
        finally
        {
            // 恢复原响应体流,避免资源泄漏
            context.Response.Body = originalResponseBody;
        }
    }
}

关键注意事项

  • 替换响应体的时机:必须在调用_next(context)之前替换,确保后续所有处理逻辑都写入我们的内存流。
  • 恢复原流:在finally块中恢复原响应体,避免资源泄漏。
  • 重置流位置:每次读取内存流前,必须用Seek(0, SeekOrigin.Begin)将位置重置到开头,否则会读取到空内容。
  • Content-Length处理:如果原响应设置了Content-Length,替换流后实际长度可能变化,ASP.NET Core会自动重新计算并设置正确的Content-Length,无需手动处理。
  • 请求体优化:添加了对请求体的判断(仅当有内容且是JSON类型时才读取),避免无意义的流操作。

内容的提问来源于stack exchange,提问作者Muharrem Servet ANKARALI

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 16:33:22