提问人:wangwang 提问时间:10/16/2023 最后编辑:marc_swangwang 更新时间:10/18/2023 访问量:55
ASP.NET Core 中 HttpResponse.TransmitFile 的替代方法
Alternative method for HttpResponse.TransmitFile in ASP.NET Core
问:
由于我想实现从我的 ASP.NET Core 后端下载大文件(> 4GB),因此许多文章指出,在 .NET Framework 中可以实现我的目标。HttpResponse.TransmitFile
但是,这似乎在 .NET Core 中不再可用。HttpResponse.TransmitFile
有谁知道 .NET Core 中的替代方案是什么?我无法告诉你我有多欣赏你的相关答案。HttpResponse.TransmitFile
答:
您可以使用以下示例来实现该要求。有关更多详细信息,您可以查看博客 Streaming Zip on ASP.NET Core。
private static HttpClient Client { get; } = new HttpClient();
[HttpGet]
public async Task<FileStreamResult> Get()
{
// get your stream
var stream = await Client.GetStreamAsync("https://raw.githubusercontent.com/StephenClearyExamples/AsyncDynamicZip/master/README.md");
return new FileStreamResult(stream, new MediaTypeHeaderValue("text/plain"))
{
FileDownloadName = "README.md"
};
}
对于 zip:
private static HttpClient Client { get; } = new HttpClient();
[HttpGet]
public IActionResult Get()
{
var filenamesAndUrls = new Dictionary<string, string>
{
{ "README.md", "https://raw.githubusercontent.com/StephenClearyExamples/AsyncDynamicZip/master/README.md" },
{ ".gitignore", "https://raw.githubusercontent.com/StephenClearyExamples/AsyncDynamicZip/master/.gitignore" },
};
return new FileCallbackResult(new MediaTypeHeaderValue("application/octet-stream"), async (outputStream, _) =>
{
using (var zipArchive = new ZipArchive(new WriteOnlyStreamWrapper(outputStream), ZipArchiveMode.Create))
{
foreach (var kvp in filenamesAndUrls)
{
var zipEntry = zipArchive.CreateEntry(kvp.Key);
using (var zipStream = zipEntry.Open())
using (var stream = await Client.GetStreamAsync(kvp.Value))
await stream.CopyToAsync(zipStream);
}
}
})
{
FileDownloadName = "MyZipfile.zip"
};
}
该解决方案具有我们以前的非核心解决方案的所有相同优势:
- 所有 I/O 都是异步的。任何时候都不会在 I/O 上阻塞任何线程。
- zip 文件未保存在内存中。它直接流式传输到客户端,即时压缩。
- 对于大文件,甚至不会将单个文件完全读入内存。每个文件都是动态单独压缩的。
评论
return File(...)
WriteOnlyStreamWrapper
This solution has all the same advantages of our previous non-Core solution
我怀疑真正的问题不是找到替代方案(它是或而是处理请求范围,以便客户端可以下载大文件,如果中断,可以重试。TransmitFile
return File(path)
return File(stream)
幸运的是,自 ASP.NET Core 2.1 以来可用的 ControllerBase.File 方法和 Minimal API(以及其他方法)中使用的 Results.File 方法都已经支持这一点。默认情况下,范围处理处于关闭状态,但可以通过传递给参数来启用,例如:true
enableRangeProcessing
public class VideoController : Controller
{
[HttpGet, Route("videos/video.mp4")]
public IActionResult Index()
{
return File("d:\Videos\video.mp4", "video/mp4", true);
}
}
更好的是,静态文件提供程序还支持开箱即用的范围(和响应压缩)。如果大文件位于特定文件夹中,则可以使用以下命令为它们提供服务:
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider("path\to\large\files"),
RequestPath = "/Videos"
});
如果要在自己的操作中使用响应压缩,则必须在 Web 服务器上或通过响应压缩中间件显式启用它:
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
});
var app = builder.Build();
app.UseResponseCompression();
从那时起,由客户端来检索特定块并重试它们。下载实用程序通常以块形式下载大文件,并自动重试失败的部分。Khalid Abuhakmeh 在一篇简短的博客文章中描述了该过程以及它如何与 ASP.NET Core 配合使用。
在 C# 中,HttpClient 可以请求文件的特定块,甚至可以使用标头同时下载它们,例如:Range
var req = new HttpRequestMessage
{
RequestUri = new Uri( url )
};
req.Headers.Range = new RangeHeaderValue( 0, 999 );
var resp = await client.SendAsync(req);
if (resp.IsSuccessStatusCode)
{
using var tempFile=File.Create("chunk.001");
await resp.Content.CopyToAsync(tempFile);
}
如果您有范围列表,则可以使用它来并行下载远程文件,并在以后合并块:
record MyRange(long Start,long End);
async Task DownloadChunkAsync(HttpClient client,Uri uri,MyRange range, CancellationToken ct)
{
var req = new HttpRequestMessage
{
RequestUri = uri
};
req.Headers.Range = new RangeHeaderValue( range.Start, range.End);
var resp = await client.SendAsync(req,ct);
if (resp.IsSuccessStatusCode)
{
using var tempFile=File.Create($"chunk.{range.Start,5}");
await resp.Content.CopyToAsync(tempFile);
}
}
var ranges=CalculateRanges(...);
var uri=new Uri( url ) ;
//Concurrent downloads
await Parallel.ForEachAsync(ranges,(range,ct)=>{
await DownloadChunkAsync(client,uri,range,ct);
}
// Combine the chunks
using(var finalStream=File.Create("finalFile.mp4"))
{
foreach(var range in ranges)
{
using var chunkStream=File.OpenRead($"chunk.{range.Start,5}");
chunkStream.CopyToAsync(filalStream);
}
}
评论
return File(myStream);
TransmitFile