创建和下载zip存档作为HttpContent时的内存使用情况 [英] Memory usage when creating and downloading zip archive as HttpContent
问题描述
我有一个Web api GET方法,该方法返回一个zip文件进行下载.这是创建zip归档文件的代码:
I have a web api GET method that returns a zip file for downloading. Here's the code that creates the zip archive:
var resultStream = new MemoryStream();
using (var zipArchive = new ZipArchive(resultStream, ZipArchiveMode.Create, leaveOpen: true))
{
foreach (var file in files)
{
zipArchive.CreateEntryFromFile(file.Path, file.Name, CompressionLevel.Optimal);
}
}
这是响应的填充方式:
var response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new ByteArrayContent(resultStream.ToArray());
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
response.Content.Headers.ContentDisposition.FileName = "export_" + DateTime.Now.ToString("dd-MM-yyyy_HH-mm-ss") + ".zip";
response.Content.Headers.ContentDisposition.CreationDate = DateTime.Now;
response.Content.Headers.ContentDisposition.Size = resultStream.Length;
response.Content.Headers.ContentLength = resultStream.Length;
上面的代码工作得很好,问题在于它会消耗服务器上的大量内存,具体取决于文件大小.我尝试将结果更改为 StreamContent
,但是由于响应仅返回标头并最终超时,因此此方法不起作用.
The code above works just fine, the problem is it consumes a lot of memory on the server, depending of course on the file sizes. I've tried changing the result to StreamContent
, however this didn't work as the response only returned headers and eventually timed out.
这是我的问题:
- 是否有办法避免将所有文件加载到内存中,而是在创建zip文件时发送该文件?
- 在这种情况下使用StreamContent更好吗?如果可以,我需要更改哪些内容才能使其正常工作?
- 缓冲在每种情况下如何影响内存消耗?我尝试通过建议
- Is there a way to avoid loading all files in memory and instead send the zip file as it gets created?
- Is using StreamContent better to use in this scenario and if yes, what do I need to change to make it work?
- How is buffering affecting memory consumption in each case? I've tried disabling buffering by implementing a custom
IHostBufferPolicySelector
as suggested in this article, but it doesn't appear to have any effect. - The api action currently can be called by navigating a link, using HttpClient or by AJAX request, so any solution has to support all scenarios.
推荐答案
Adapted from the Kudu
project, a method that uses PushStreamContent
in combination with a specific DelegatingStream
wrapper to stream a zip archive:
public static class ZipStreamContent
{
public static PushStreamContent Create(string fileName, Action<ZipArchive> onZip)
{
var content = new PushStreamContent((outputStream, httpContent, transportContext) =>
{
using (var zip = new ZipArchive(new StreamWrapper(outputStream), ZipArchiveMode.Create, leaveOpen: false))
{
onZip(zip);
}
});
content.Headers.ContentType = new MediaTypeHeaderValue("application/zip");
content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
content.Headers.ContentDisposition.FileName = fileName;
return content;
}
// this wraps the read-only HttpResponseStream to support ZipArchive Position getter.
public class StreamWrapper : DelegatingStream
{
private long _position = 0;
public StreamWrapper(Stream stream)
: base(stream)
{
}
public override long Position
{
get { return _position; }
set { throw new NotSupportedException(); }
}
public override void Write(byte[] buffer, int offset, int count)
{
_position += count;
base.Write(buffer, offset, count);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
_position += count;
return base.BeginWrite(buffer, offset, count, callback, state);
}
}
}
对于您的情况,您可以这样使用:
Which for your case you could use like:
var response = new HttpResponseMessage(HttpStatusCode.OK);
var response.Content = ZipStreamContent.Create(
"export_" + DateTime.Now.ToString("dd-MM-yyyy_HH-mm-ss") + ".zip",
zipArchive => {
foreach (var file in files)
{
zipArchive.CreateEntryFromFile(file.Path, file.Name, CompressionLevel.Optimal);
}
});
这篇关于创建和下载zip存档作为HttpContent时的内存使用情况的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!