2013-01-29 17 views
14

Wie kann ich einen Web-API-Controller erstellen, der eine komprimierte Zip-Datei generiert und zurückgibt, die aus einer Sammlung von speicherinternen JPEG-Dateien (MemoryStream-Objekten) gestreamt wurde. Ich versuche die DotNetZip-Bibliothek zu verwenden. Ich habe dieses Beispiel gefunden: http://www.4guysfromrolla.com/articles/092910-1.aspx#postadlink. Aber die Response.OutputStream ist nicht verfügbar in Web-API und so Technik funktioniert nicht ganz. Daher habe ich versucht, die Zip-Datei in einem neuen MemoryStream zu speichern; aber es hat geworfen. Zuletzt habe ich versucht, PushStreamContent zu verwenden. Hier ist mein Code:Wie kann ein Controller mithilfe der ASP.NET-Web-API eine Sammlung gestreamter Bilder zurückgeben, die mit der DotNetZip-Bibliothek komprimiert wurden?

public HttpResponseMessage Get(string imageIDsList) { 
     var imageIDs = imageIDsList.Split(',').Select(_ => int.Parse(_)); 
     var any = _dataContext.DeepZoomImages.Select(_ => _.ImageID).Where(_ => imageIDs.Contains(_)).Any(); 
     if (!any) { 
      throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)); 
     } 
     var dzImages = _dataContext.DeepZoomImages.Where(_ => imageIDs.Contains(_.ImageID)); 
     using (var zipFile = new ZipFile()) { 
      foreach (var dzImage in dzImages) { 
       var bitmap = GetFullSizeBitmap(dzImage); 
       var memoryStream = new MemoryStream(); 
       bitmap.Save(memoryStream, ImageFormat.Jpeg); 
       var fileName = string.Format("{0}.jpg", dzImage.ImageName); 
       zipFile.AddEntry(fileName, memoryStream); 
      } 
      var response = new HttpResponseMessage(HttpStatusCode.OK); 
      var memStream = new MemoryStream(); 
      zipFile.Save(memStream); //Null Reference Exception 
      response.Content = new ByteArrayContent(memStream.ToArray()); 
      response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); 
      response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = string.Format("{0}_images.zip", dzImages.Count()) }; 
      return response; 
     } 
    } 

zipFile.Save (memStream) wirft NULL-Verweis. Aber weder zipFile noch memStream sind null und es gibt keine interne Ausnahme. Ich bin mir also nicht sicher, was die Nullreferenz verursacht. Ich habe sehr wenig Erfahrung mit Web-API, Speicher-Streams, und ich habe noch nie zuvor DotNetZipLibrary verwendet. Dies ist ein Follow-up zu dieser Frage: Want an efficient ASP.NET Web API controller that can reliably return 30 to 50 ~3MB JPEGs

Irgendwelche Ideen? Vielen Dank!

+0

ich nichts falsch mit Ihrem WebAPI Code. Allerdings würde ich StreamContent anstelle von ByteArrayContent verwenden. Ich weiß, du hast gesagt, du bekommst den NULL-Verweis bei der zipFile.Save, aber ich würde versuchen, die using() um das neue ZipFile() zu entfernen. –

+0

Danke. Leider half das Entfernen der Verwendung nicht. Es wirft immer noch NullReferenceException bei zipFile.Save (...). Hier ist die Spur: bei Ionic.Zlib.ParallelDeflateOutputStream._Flush (Boolean lastInput) bei Ionic.Zlib.ParallelDeflateOutputStream.Close() bei Ionic.Zip.ZipEntry.FinishOutputStream (Stream s, CountingStream entryCounter, Stream Verschlüssler, Stream Kompressor, CrcCalculatorStream Ausgabe) bei Ionic.Zip.ZipEntry._WriteEntryData (Stream s) bei Ionic.Zip.ZipEntry.Write (Stream s) bei Ionic.Zip.ZipFile.Save() bei Ionic.Zip.ZipFile.Save (Stream output) – CalvinDale

+0

Nach bitmap.Save tun, was ist die Position von memoryStream? Möglicherweise müssen Sie memoryStream.Position = 0 setzen, sonst könnten Sie null Bytes in die Zip-Datei speichern. –

Antwort

6

Die PushStreamContent Klasse kann in diesem Fall verwendet werden, um die Notwendigkeit für den MemoryStream zu beseitigen, zumindest für die gesamte Zip-Datei. Es kann wie folgt realisiert werden:

public HttpResponseMessage Get(string imageIDsList) 
    { 
     var imageIDs = imageIDsList.Split(',').Select(_ => int.Parse(_)); 
     var any = _dataContext.DeepZoomImages.Select(_ => _.ImageID).Where(_ => imageIDs.Contains(_)).Any(); 
     if (!any) 
     { 
      throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound)); 
     } 
     var dzImages = _dataContext.DeepZoomImages.Where(_ => imageIDs.Contains(_.ImageID)); 
     var streamContent = new PushStreamContent((outputStream, httpContext, transportContent) => 
      { 
       try 
       { 
        using (var zipFile = new ZipFile()) 
        { 
         foreach (var dzImage in dzImages) 
         { 
          var bitmap = GetFullSizeBitmap(dzImage); 
          var memoryStream = new MemoryStream(); 
          bitmap.Save(memoryStream, ImageFormat.Jpeg); 
          memoryStream.Position = 0; 
          var fileName = string.Format("{0}.jpg", dzImage.ImageName); 
          zipFile.AddEntry(fileName, memoryStream); 
         } 
         zipFile.Save(outputStream); //Null Reference Exception 
        } 
       } 
       finally 
       { 
        outputStream.Close(); 
       } 
      }); 
     streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); 
     streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") 
     { 
      FileName = string.Format("{0}_images.zip", dzImages.Count()), 
     }; 

     var response = new HttpResponseMessage(HttpStatusCode.OK) 
      { 
       Content = streamContent 
      }; 

     return response; 
    } 

Idealerweise würde es möglich sein, diese auch dynamisch erstellt mit der ZipOutputStream Klasse zu machen, um dynamisch die zip zu erstellen, anstatt ZipFile zu verwenden. In diesem Fall wird der MemoryStream für jede Bitmap nicht benötigt.

15

Ein generischer Ansatz würde wie folgt funktionieren:

using Ionic.Zip; // from NUGET-Package "DotNetZip" 

public HttpResponseMessage Zipped() 
{ 
    using (var zipFile = new ZipFile()) 
    { 
     // add all files you need from disk, database or memory 
     // zipFile.AddEntry(...); 

     return ZipContentResult(zipFile); 
    } 
} 

protected HttpResponseMessage ZipContentResult(ZipFile zipFile) 
{ 
    // inspired from http://stackoverflow.com/a/16171977/92756 
    var pushStreamContent = new PushStreamContent((stream, content, context) => 
    { 
     zipFile.Save(stream); 
     stream.Close(); // After save we close the stream to signal that we are done writing. 
    }, "application/zip"); 

    return new HttpResponseMessage(HttpStatusCode.OK) {Content = pushStreamContent}; 
} 

Die ZipContentResult Methode könnte auch in einer Basisklasse lebt und von einer anderen Aktion in jedem api-Controller verwendet werden.

+2

das funktioniert super. Ich weiß nicht, warum das unbemerkt blieb – Jonesopolis

+0

@Felix Alcala dieser Code funktioniert gut, aber ich bekomme eine beschädigte zip, jede Idee, warum? – user3378165

+0

@ user3378165 Leider nicht. Es hat auf unserem Server ohne Probleme funktioniert, seit ich es gepostet habe. Dennoch ist C# -Zip-Behandlung immer eine Art von Schmerz. Einige Startpunkte könnten sein: Ist der Download abgeschnitten? Vielleicht enthalten die Dateinamen Nicht-US-Zeichen und müssen möglicherweise besondere Berücksichtigung finden? Können Sie mehr über die Korruption erfahren (z. B. Arbeit in Windows Explorer, aber nicht in 7zip)? Beim Öffnen von Zips in Mono/Xamarin müssen spezielle Build-Einstellungen vorgenommen werden usw. –

0

Ich hatte gerade das gleiche Problem wie Sie.

zipFile.Save(outputStream); //I got Null Reference Exception here. 

Das Problem war, dass ich Dateien von einem Speicherstrom wurde das Hinzufügen wie so:

zip.AddEntry(fileName, ms); 

Alles, was Sie tun müssen, ist es so weit zu ändern:

zip.AddEntry(fileName, ms.ToArray()); 

Es scheint, dass Wenn der Verfasser beschließt, die Datei tatsächlich zu schreiben, und versucht, den Datenstrom zu lesen, wird der Datenstrom gesammelt oder ...

Prost!

1
public HttpResponseMessage GetItemsInZip(int id) 
    {   
      var itemsToWrite = // return array of objects based on id; 

      // create zip file stream 
      MemoryStream archiveStream = new MemoryStream(); 
      using (ZipArchive archiveFile = new ZipArchive(archiveStream, ZipArchiveMode.Create, true)) 
      { 
       foreach (var item in itemsToWrite) 
       { 
        // create file streams 
        // add the stream to zip file 

        var entry = archiveFile.CreateEntry(item.FileName); 
        using (StreamWriter sw = new StreamWriter(entry.Open())) 
        { 
         sw.Write(item.Content); 
        } 
       } 
      } 

      // return the zip file stream to http response content     
      HttpResponseMessage responseMsg = new HttpResponseMessage(HttpStatusCode.OK);     
      responseMsg.Content = new ByteArrayContent(archiveStream.ToArray()); 
      archiveStream.Dispose(); 
      responseMsg.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "test.zip" }; 
      responseMsg.Content.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); 

      return responseMsg;   
    } 

Framework verwendet .NET 4.6.1 mit MVC 5