Utilizzo della memoria durante la creazione e il download di archivi zip come HttpContent

Ho un metodo GET web API che restituisce un file zip per il download. Ecco il codice che crea l’archivio 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); } } 

Ed ecco come viene popolata la risposta:

 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; 

Il codice sopra funziona perfettamente, il problema è che consuma molta memoria sul server, a seconda delle dimensioni del file. Ho provato a cambiare il risultato in StreamContent , tuttavia questo non ha funzionato in quanto la risposta ha restituito solo intestazioni e alla fine scaduto.

Quindi ecco le mie domande:

  1. C’è un modo per evitare di caricare tutti i file in memoria e invece inviare il file zip man mano che viene creato?
  2. Sta usando StreamContent meglio da usare in questo scenario e se sì, cosa devo cambiare per farlo funzionare?
  3. In che modo il buffering influisce sul consumo di memoria in ciascun caso? Ho provato a disabilitare il buffering implementando un custom IHostBufferPolicySelector come suggerito in questo articolo , ma non sembra avere alcun effetto.
  4. L’azione API attualmente può essere richiamata navigando un collegamento, utilizzando HttpClient o la richiesta AJAX, quindi qualsiasi soluzione deve supportare tutti gli scenari.

Adattato dal progetto Kudu , un metodo che utilizza PushStreamContent in combinazione con uno specifico wrapper DelegatingStream per lo streaming di un archivio zip:

 public static class ZipStreamContent { public static PushStreamContent Create(string fileName, Action 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); } } } 

Che per il tuo caso potresti usare come:

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