C# 创建zip存档并将其下载为HttpContent时的内存使用情况

C# 创建zip存档并将其下载为HttpContent时的内存使用情况,c#,asp.net-web-api,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.CreateE

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

下面是我的问题:

  • 有没有办法避免在内存中加载所有文件,而是在创建zip文件时发送它
  • 在这种情况下使用StreamContent是否更好?如果是,我需要做哪些更改才能使其正常工作
  • 在每种情况下,缓冲是如何影响内存消耗的?我尝试过按照建议通过实现自定义的
    IHostBufferPolicySelector
    来禁用缓冲,但似乎没有任何效果
  • 当前可以通过导航链接、使用HttpClient或通过AJAX请求调用api操作,因此任何解决方案都必须支持所有场景
  • 改编自该项目的一种方法,该方法使用
    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);
            }
        }
    }
    

    尝试使用GZipStream和PushStreamContent您在服务器上收到多少请求?您将无法设置内容长度,因此需要启用区块编码。@AliHasan最多2-3个并发请求。这是一个内部应用程序,因此,使用ByteArrayContent仍然是一个选项。您是否有任何未处理的对象?谢谢您,Alex,什么是DelegatingStream以及如何使用它?呃,您可能需要对此稍加推敲:ASP.NET堆栈中的
    内部
    抽象类(命名空间
    System.NET.Http
    ),您可以找到它的源代码,您刚刚尝试过它,它似乎是一个很好的解决方案,
    PushStreamContent
    ,内存使用率现在非常低。但是,每当zip方法中出现异常时,我就会遇到一个问题,响应失败,但响应状态仍然是200,不幸的是,这会中断任何AJAX调用。@elolos我不确定。我认为你不能在事后设置响应代码。如果省略构造函数中的状态代码,只执行
    var response=new-HttpResponseMessage()
    ,则异常似乎不会发送到客户端,因为它们发生在客户端收到200 OK响应之后。因此,我担心,即使从内存的角度来看,这是一个很好的解决方案,但在我的代码必须使用的上下文中,它并没有那么有用。
    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);
            }        
        });