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()));
没有。你永远不能直接这样做。
请注意,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);
}
作为一种变通方法,您可以将字节复制到原始流中,如下所示:
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);
在 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()));
没有。你永远不能直接这样做。
请注意,
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); }
作为一种变通方法,您可以将字节复制到原始流中,如下所示:
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);