如何在 ASP.NET Core 中间件中直接将响应正文设置为文件流?

Posted

技术标签:

【中文标题】如何在 ASP.NET Core 中间件中直接将响应正文设置为文件流?【英文标题】:How to directly set response body to a file stream in ASP.NET Core middleware? 【发布时间】:2020-02-27 09:59:44 【问题描述】:

以下用于在 ASP.NET Core 中间件中将文件流写入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 是对 initialized before 对象 (HttpResponseStream) 的引用,它在 HttpContext 中可用。假设所有字节都写入这个原始流。如果您将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);
        
    

    作为一种解决方法,您可以将字节复制到原始流,如下所示:

    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;
        
    

【讨论】:

感谢您的解释。对于文件内容响应,我认为httpContext.Response.SendFileAsync 也可以,完全不需要FileStream @Nick 是的,当然。上面的CopyToAsync 只是说明如何处理普通Stream 的一种方式。如果您正在处理文件,请随意使用更简单的方法:)【参考方案2】:

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

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

【讨论】:

以上是关于如何在 ASP.NET Core 中间件中直接将响应正文设置为文件流?的主要内容,如果未能解决你的问题,请参考以下文章

Error Handling in ASP.NET Core

在 ASP.NET Core 响应后做一些工作

ASP.NET Core Web API - 如何在中间件管道中隐藏 DbContext 事务?

ASP.Net Core 2.0 如何获取中间件中的所有请求标头? [复制]

如何使用 MVC 的内容协商在 ASP.NET Core MVC 中间件中返回响应?

如何在 asp net core 2.2 中间件中多次读取请求正文?