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

.NET 8中间件读取响应时控制台输出异常问题排查

.NET 8响应读取中间件控制台输出异常问题分析

我在.NET 8中编写了一个读取请求响应的中间件,代码如下:

public async Task InvokeAsync(HttpContext context)
{
    var originalBodyStream = context.Response.Body;

    using (var responseBody = new MemoryStream())
    {
        context.Response.Body = responseBody;

        await _next(context);

        context.Response.Body.Seek(0, SeekOrigin.Begin);
        var responseText = await new StreamReader(context.Response.Body).ReadToEndAsync();
        context.Response.Body.Seek(0, SeekOrigin.Begin);

        Console.WriteLine("Response Body:");
        Console.WriteLine(responseText);

        await responseBody.CopyToAsync(originalBodyStream);
    }
}

同时编写了测试控制器模拟真实场景:

[HttpGet("sample")]
public IActionResult GetSample()
{
    var s = "[\r\n {    \"id\": \"test\",\r\n \"id\": \"test\"   } ]";
    return Content(s, "application/json; charset=utf-8");
}

该控制器返回结果在控制台输出正确,但使用以下控制器代码时,控制台输出响应异常:

[HttpGet("sample")]
public IActionResult GetSample()
{
    var s = "[\r\n    {\r\n        \"id\": \"test\",\r\n        \"tt\": \"213\",\r\n        \"nn\": \"33\"\r\n    }\r\n]";
    return Content(s, "application/json; charset=utf-8");
}

两种情况在Postman中均能正常显示响应,请问异常原因是什么?


异常原因分析

问题核心在于StreamReader的内部行为与流位置管理冲突:

  1. StreamReader读取流时,会自动检测编码并使用内部缓冲,读取完成后会将流的位置直接移动到末尾。你虽然在读取前调用了Seek(0, SeekOrigin.Begin),但ReadToEndAsync执行后,流的位置已经到了末端,后续的Seek操作无法完全恢复StreamReader对原流的影响。
  2. 第二个控制器返回的内容更长,触发了StreamReader的缓冲机制,导致流位置偏移的问题被放大,而短内容场景刚好未触发该缓冲逻辑,所以控制台输出正常。
  3. Postman能正常显示是因为最终将responseBody完整复制回了原始流,中间读取时的流位置异常只影响了控制台输出,不影响最终响应内容。

修复方案

方案1:使用临时流隔离StreamReader的影响

public async Task InvokeAsync(HttpContext context)
{
    var originalBodyStream = context.Response.Body;

    using (var responseBody = new MemoryStream())
    {
        context.Response.Body = responseBody;

        await _next(context);

        responseBody.Seek(0, SeekOrigin.Begin);
        // 用临时流承载内容,避免StreamReader修改原响应流位置
        using (var tempStream = new MemoryStream())
        {
            await responseBody.CopyToAsync(tempStream);
            tempStream.Seek(0, SeekOrigin.Begin);
            var responseText = await new StreamReader(tempStream).ReadToEndAsync();
            Console.WriteLine("Response Body:");
            Console.WriteLine(responseText);
        }
        
        responseBody.Seek(0, SeekOrigin.Begin);
        await responseBody.CopyToAsync(originalBodyStream);
    }
}

方案2:直接读取字节数组(更简洁)

public async Task InvokeAsync(HttpContext context)
{
    var originalBodyStream = context.Response.Body;

    using (var responseBody = new MemoryStream())
    {
        context.Response.Body = responseBody;

        await _next(context);

        responseBody.Seek(0, SeekOrigin.Begin);
        byte[] responseBytes = await responseBody.ToArrayAsync();
        var responseText = Encoding.UTF8.GetString(responseBytes);
        
        Console.WriteLine("Response Body:");
        Console.WriteLine(responseText);

        responseBody.Seek(0, SeekOrigin.Begin);
        await responseBody.CopyToAsync(originalBodyStream);
    }
}

内容的提问来源于stack exchange,提问作者Drezzz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 11:33:15