2013-01-29 7 views
14

Come è possibile creare un controller API Web che genera e restituisce un file zip compresso in streaming da una raccolta di file JPEG in memoria (oggetti MemoryStream). Sto tentando di usare DotNetZip Library. Ho trovato questo esempio: http://www.4guysfromrolla.com/articles/092910-1.aspx#postadlink. Tuttavia, Response.OutputStream non è disponibile nell'API Web e pertanto la tecnica non funziona. Pertanto ho provato a salvare il file zip in un nuovo MemoryStream; ma ha gettato. Infine, ho provato a utilizzare PushStreamContent. Ecco il mio codice:Utilizzando l'API Web ASP.NET, in che modo un controller può restituire una raccolta di immagini in streaming compresse utilizzando DotNetZip Library?

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) genera riferimento null. Ma né zipFile né memStream sono nulli e non ci sono eccezioni interne. Quindi non sono sicuro di cosa stia causando il riferimento null. Ho poca esperienza con le API Web, i flussi di memoria e non ho mai usato DotNetZipLibrary in precedenza. Questo è il seguito di questa domanda: Want an efficient ASP.NET Web API controller that can reliably return 30 to 50 ~3MB JPEGs

Qualche idea? Grazie!

+0

Non vedo niente di sbagliato con il codice WebAPI. Tuttavia, utilizzerei StreamContent anziché ByteArrayContent. Mi rendo conto che hai detto di ottenere il riferimento null al file zipFile.Save, ma proverei a rimuovere use() attorno al nuovo ZipFile(). –

+0

Grazie. Purtroppo rimuovere l'utilizzo non ha aiutato. Getti ancora NullReferenceException su zipFile.Save (...). Ecco la traccia: at Ionic.Zlib.ParallelDeflateOutputStream._Flush (Boolean lastInput) in Ionic.Zlib.ParallelDeflateOutputStream.Close() in Ionic.Zip.ZipEntry.FinishOutputStream (Stream s, CountingStream entryCounter, Stream encryptor, Stream compressore, CrcCalculatorStream output) in Ionic.Zip.ZipEntry._WriteEntryData (Stream s) a Ionic.Zip.ZipEntry.Write (Stream s) a Ionic.Zip.ZipFile.Save() a Ionic.Zip.ZipFile.Save (Stream outputStream) – CalvinDale

+0

Dopo aver fatto bitmap.Save, cosa è la posizione di memoryStream? Potrebbe essere necessario impostare memoryStream.Position = 0 altrimenti si potrebbero memorizzare zero byte nel file zip. –

risposta

6

La classe PushStreamContent può essere utilizzata in questo caso per eliminare la necessità di MemoryStream, almeno per l'intero file zip. Può essere implementato in questo modo:

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

Idealmente sarebbe possibile per rendere questo ancora creato più dinamico utilizzando la classe ZipOutputStream per creare dinamicamente la zip invece di utilizzare ZipFile. In tal caso, il MemoryStream per ogni bitmap non sarebbe necessario.

15

Un approccio più generico dovrebbe funzionare in questo modo:

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

Procedimento ZipContentResult potrebbe anche vivere in una classe base ed essere usata da qualsiasi altra azione in qualsiasi controllore api.

+2

che funziona benissimo. Non so perché questo è passato inosservato – Jonesopolis

+0

@Felix Alcala questo codice funziona alla grande, ma sto ricevendo un file zip danneggiato, qualche idea del perché? – user3378165

+0

@ user3378165 Purtroppo no. Sta funzionando sul nostro server senza alcun problema da quando l'ho pubblicato. Eppure, la manipolazione delle caramelle C# è sempre una sorta di dolore. Alcuni punti di partenza potrebbero essere: il download è troncato? Forse i nomi dei file contengono caratteri non statunitensi e potrebbe richiedere una considerazione speciale? Puoi saperne di più sulla corruzione (ad esempio, lavoro in Windows Explorer, ma non in 7zip)? Quando apri le zip in mono/Xamarin, è necessario impostare impostazioni speciali di creazione, ecc. –

0

Ho appena avuto lo stesso problema di te.

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

Il problema era che stavo aggiungendo i file da un flusso di memoria in questo modo:

zip.AddEntry(fileName, ms); 

Tutto quello che dovete fare è cambiare a questo:

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

Sembra che quando lo scrittore decide di scrivere effettivamente il file e prova a leggere lo stream, lo stream è garbage collection o sth ...

Saluti!

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

Usato .NET Framework 4.6.1 con MVC 5

Problemi correlati