2013-01-29 16 views
14

W jaki sposób można utworzyć kontroler interfejsu Web API, który generuje i zwraca skompresowany plik ZIP przesyłany strumieniowo z kolekcji plików JPEG w pamięci (obiekty MemoryStream). Próbuję użyć biblioteki DotNetZip. Znalazłem ten przykład: http://www.4guysfromrolla.com/articles/092910-1.aspx#postadlink. Ale Response.OutputStream nie jest dostępny w Web API i dlatego technika ta nie działa. Dlatego próbowałem zapisać plik zip do nowego MemoryStream; ale rzucił. Wreszcie próbowałem używać PushStreamContent. Oto mój kod:Korzystając z interfejsu ASP.NET Web API, w jaki sposób kontroler może zwrócić kolekcję strumieniowych obrazów skompresowanych przy użyciu biblioteki DotNetZip?

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) wyrzuca pustą referencję. Ale ani zipFile, ani memStream nie są puste i nie ma wewnętrznego wyjątku. Więc nie jestem pewien, co powoduje odwołanie zerowe. Mam bardzo małe doświadczenie z Web API, strumieniami pamięci i nigdy wcześniej nie korzystałem z DotNetZipLibrary. Jest to kontynuacja tego pytania: Want an efficient ASP.NET Web API controller that can reliably return 30 to 50 ~3MB JPEGs

Wszelkie pomysły? dzięki!

+0

nie widzę niczego złego w kodzie WebAPI. Jednak użyłbym StreamContent zamiast ByteArrayContent. Rozumiem, że powiedziałeś, że uzyskałeś referencję zerową w pliku zipFile.Save, ale spróbowałbym usunąć use() wokół nowego pliku ZipFile(). –

+0

Dzięki. Niestety usunięcie użycia nie pomogło. Nadal rzuca wyjątek NullReferenceException na zipFile.Save (...). Oto ślad: na Ionic.Zlib.ParallelDeflateOutputStream._Flush (Boolean lastInput) w Ionic.Zlib.ParallelDeflateOutputStream.Close() w Ionic.Zip.ZipEntry.FinishOutputStream (strumień s, CountingStream entryCounter szyfratora, potok, strumień sprężarki, CrcCalculatorStream wyjście) w Ionic.Zip.ZipEntry._WriteEntryData (Stream s) na Ionic.Zip.ZipEntry.Write (Stream s) na Ionic.Zip.ZipFile.Save() na Ionic.Zip.ZipFile.Save (Stream outputStream) – CalvinDale

+0

Po wykonaniu bitmap.Save, co jest pozycja memoryStream? Może być konieczne ustawienie parametru memoryStream.Position = 0, ponieważ w przeciwnym razie można zapisać zero-bajtów w pliku zip. –

Odpowiedz

6

W tym przypadku można użyć klasy PushStreamContent, aby wyeliminować konieczność użycia strumienia MemoryStream, przynajmniej dla całego pliku zip. Może on być realizowany tak:

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

Idealnie byłoby możliwe, aby ten jeszcze bardziej dynamicznie tworzone przy użyciu klasy ZipOutputStream do dynamicznego tworzenia zip zamiast używania zipfile. W takim przypadku MemoryStream dla każdej bitmapy nie byłby potrzebny.

15

Bardziej ogólne podejście będzie działać tak:

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

Sposób ZipContentResult może również żyć w klasie bazowej i będą używane z jakiegokolwiek innego działania w dowolnym kontrolerze API.

+2

który działa świetnie. Nie wiem, dlaczego to poszło niezauważone – Jonesopolis

+0

@Felix Alcala ten kod działa świetnie, ale dostaję uszkodzony zip, jakiś pomysł, dlaczego? – user3378165

+0

@ user3378165 Niestety nie. Pracuje na naszym serwerze bez problemu, odkąd go opublikowałem. Jednak obsługa C# -zip jest zawsze pewnym rodzajem bólu. Niektóre punkty początkowe mogą być następujące: czy pobieranie jest skrócone? Może nazwy plików zawierają znaki spoza Stanów Zjednoczonych i mogą wymagać specjalnej uwagi? Czy możesz dowiedzieć się więcej o korupcji (np. Pracować w Eksploratorze Windows, ale nie w 7zip)? Podczas otwierania zamków w mono/Xamarin należy ustawić specjalne ustawienia kompilacji itp. –

0

Po prostu miałem ten sam problem co ty.

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

Problemem było to, że dodawanie plików ze strumienia pamięci tak:

zip.AddEntry(fileName, ms); 

Wszystko co musisz zrobić, to zmienić go na to:

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

Wydaje się, że gdy pisarz postanawia faktycznie zapisać plik i próbuje odczytać strumień, strumień jest zbiorem śmieci lub ...

Pozdrawiam!

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

Używane .NET Framework 4.6.1 z MVC 5

Powiązane problemy