创建和下载zip存档为HttpContent时的内存使用情况

时间:2015-04-21 14:40:57

标签: c# asp.net-web-api

我有一个web api GET方法,它返回一个zip文件供下载。这是创建zip存档的代码:

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,但这不起作用,因为响应只返回标题并最终超时。

所以这是我的问题:

  1. 有没有办法避免在内存中加载所有文件,而是在创建时发送zip文件?
  2. 在这种情况下是否更好地使用StreamContent,如果是,我需要更改什么才能使其正常工作?
  3. 在每种情况下,缓冲如何影响内存消耗?我已经尝试通过按照建议的in this article实施自定义IHostBufferPolicySelector来禁用缓冲,但它似乎没有任何效果。
  4. 目前可以通过导航链接,使用HttpClient或AJAX请求来调用api操作,因此任何解决方案都必须支持所有方案。

1 个答案:

答案 0 :(得分:3)

改编自Kudu项目,该方法使用PushStreamContent与特定DelegatingStream包装器组合来流式传输zip存档:

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);
        }
    }
}

根据您的情况,您可以使用:

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);
        }        
    });