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

elo*_*los 9 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是响应如何填充:

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;
Run Code Online (Sandbox Code Playgroud)

上面的代码运行得很好,问题是它在服务器上消耗了大量内存,当然这取决于文件大小.我已经尝试将结果更改为StreamContent,但这不起作用,因为响应只返回标题并最终超时.

所以这是我的问题:

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

Ale*_*lex 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

对于您的情况,您可以使用如下:

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);
        }        
    });
Run Code Online (Sandbox Code Playgroud)

  • 呃,您可能需要稍微处理一下:ASP.NET 堆栈中的“内部”抽象类(命名空间“System.Net.Http”),您可以在[此处](http://referencesource.microsoft .com/#System.Net.Http/System/Net/Http/DelegatingStream.cs) (2认同)