ASP.NET核心中间件如何直接将响应体设置为文件流?

How to directly set response body to a file stream in ASP.NET Core middleware?

在 ASP.NET 核心中间件中将文件流写入 Response.Body 的示例代码不起作用(发出空响应):

public Task Invoke(HttpContext context)
{
    context.Response.ContentType = "text/plain";

    using (var fs = new FileStream("/valid-path-to-file-on-server.txt", FileMode.Open)
    using (var sr = new StreamReader(fs))
    {
        context.Response.Body = sr.BaseStream;
    }

    return Task.CompletedTask;
}

知道这种直接设置 context.Response.Body 的方法有什么问题吗?

注意:管道中的任何下一个中间件都将被跳过,不再进行进一步处理。

更新(另一个例子):一个简单的MemoryStream赋值也不起作用(空响应):

context.Response.Body = new MemoryStream(Encoding.UTF8.GetBytes(DateTime.Now.ToString()));
  1. 没有。你永远不能直接这样做。

    请注意,context.Response.Body 是对对象的引用(HttpResponseStream) that is initialized before 它在 HttpContext 中可用。假设所有字节都写入此原始Stream。如果你改变 Body 来引用( 指向 )一个新的流对象 context.Response.Body = a_new_Stream,原来的 Stream 根本没有改变.

    此外,如果您查看 ASP.NET Core 的源代码,您会发现团队总是 将包装流 复制到最后的原始主体流而不是简单的替换(除非他们使用模拟流进行单元测试)。例如SPA Prerendering middleware源代码:

        finally
        {
            context.Response.Body = originalResponseStream;
            ...
    

    ResponseCachingMiddleware源代码:

        public async Task Invoke(HttpContext httpContext)
        {
            ...
            finally
            {
                UnshimResponseStream(context);
            }
            ...
        }
    
        internal static void UnshimResponseStream(ResponseCachingContext context)
        {
            // Unshim response stream
            context.HttpContext.Response.Body = context.OriginalResponseStream;
    
            // Remove IResponseCachingFeature
            RemoveResponseCachingFeature(context.HttpContext);
        }
    
  2. 作为一种变通方法,您可以将字节复制到原始流中,如下所示:

    public async Task Invoke(HttpContext context)
    {
        context.Response.ContentType = "text/plain";
        using (var fs = new FileStream("valid-path-to-file-on-server.txt", FileMode.Open))
        {
            await fs.CopyToAsync(context.Response.Body);
        }
    }
    

    或者,如果您想使用自己的流包装器劫持原始HttpResponseStream

        var originalBody = HttpContext.Response.Body;
        var ms = new MemoryStream();
        HttpContext.Response.Body = ms;
        try
        {
            await next();
            HttpContext.Response.Body = originalBody;
            ms.Seek(0, SeekOrigin.Begin);
            await ms.CopyToAsync(HttpContext.Response.Body);
        }
        finally
        {
            response.Body = originalBody;
        }
    

问题中的 using 语句导致您的流和流 reader 相当短暂,因此它们都将被处理掉。 “body”中对蒸汽的额外引用不会阻止处理。

框架在发送响应后处理流。 (媒介就是消息)。

在 net 6 中,我发现当我尝试执行此操作时出现控制台错误,例如:

System.InvalidOperationException: Response Content-Length mismatch: too many bytes written (25247 of 8863).

解决方案是删除相关的 header:

context.Response.Headers.Remove("Content-Length");
await context.Response.SendFileAsync(filename);