2012-10-16 19 views
5

Ich versuche, eine ZIP-Datei in einer MVC-Methode mit den DotNetZip-Komponenten zu erstellen.Verwendung von MemoryStream und DotNetZip in MVC gibt "Kann nicht auf einen geschlossenen Stream zugreifen"

Hier ist mein Code:

public FileResult DownloadImagefilesAsZip() 
    { 
     using (var memoryStream = new MemoryStream()) 
     { 
      using (var zip = new ZipFile()) 
      { 
       zip.AddDirectory(Server.MapPath("/Images/")); 
       zip.Save(memoryStream); 

       return File(memoryStream, "gzip", "images.zip"); 
      } 
     } 
    } 

Wenn ich es laufen bekomme ich einen Fehler „Kann nicht ein geschlossenes Stream-Zugang“, und ich bin mir nicht sicher, warum.

Antwort

15

Sie das MemoryStream nicht entsorgen, die FileStreamResult darauf achten wird, sobald sie es auf die Antwort fertig geschrieben hat:

public ActionResult DownloadImagefilesAsZip() 
{ 
    var memoryStream = new MemoryStream(); 
    using (var zip = new ZipFile()) 
    { 
     zip.AddDirectory(Server.MapPath("~/Images")); 
     zip.Save(memoryStream); 
     return File(memoryStream, "application/gzip", "images.zip"); 
    } 
} 

Durch die Art und Weise würde ich empfehlen Sie eine benutzerdefinierte Aktion Ergebnis zu schreiben, dies zu umgehen, anstatt Klempnercode in Ihre Controller-Aktion schreiben. Nicht nur, dass Sie ein wiederverwendbares Aktionsergebnis erhalten, sondern auch, dass Ihr Code sehr ineffizient ist => Sie führen den ZIP-Vorgang im Speicher aus und laden so den gesamten ~/images Verzeichnisinhalt + die ZIP-Datei im Speicher. Wenn Sie viele Benutzer und viele Dateien in diesem Verzeichnis haben, wird Ihnen sehr schnell der Speicher ausgehen.

Eine viel effizientere Lösung ist direkt in den Antwortstream zu schreiben:

public class ZipResult : ActionResult 
{ 
    public string Path { get; private set; } 
    public string Filename { get; private set; } 

    public ZipResult(string path, string filename) 
    { 
     Path = path; 
     Filename = filename; 
    } 

    public override void ExecuteResult(ControllerContext context) 
    { 
     if (context == null) 
     { 
      throw new ArgumentNullException("context"); 
     } 

     var response = context.HttpContext.Response; 
     response.ContentType = "application/gzip"; 
     using (var zip = new ZipFile()) 
     { 
      zip.AddDirectory(Path); 
      zip.Save(response.OutputStream); 
      var cd = new ContentDisposition 
      { 
       FileName = Filename, 
       Inline = false 
      }; 
      response.Headers.Add("Content-Disposition", cd.ToString()); 
     } 
    } 
} 

und dann:

public ActionResult DownloadImagefilesAsZip() 
{ 
    return new ZipResult(Server.MapPath("~/Images"), "images.zip"); 
} 
+0

Perfect! Vielen Dank :) –

+1

Ich musste 'memoryStream.Seek (0, SeekOrigin.Begin);' vor 'return File (...);' hinzufügen, damit es funktioniert. – pqvst

0

Kommentar konnte nicht.

Die Antwort von Darin ist großartig! Immer noch eine Speicherausnahme erhalten, so musste response.BufferOutput = false hinzufügen; und deswegen musste der Content-Disposition-Code höher werden. So

Sie haben:

... 
     var response = context.HttpContext.Response; 
     response.ContentType = "application/zip"; 
     response.BufferOutput = false; 

     var cd = new ContentDisposition 
     { 
      FileName = ZipFilename, 
      Inline = false 
     }; 
     response.Headers.Add("Content-Disposition", cd.ToString()); 

     using (var zip = new ZipFile()) 
     { 
... 

Nur für den Fall, es war nicht offensichtlich :)