Comment créer un contrôleur API Web qui génère et renvoie un fichier zip compressé diffusé à partir d’une collection de fichiers JPEG en mémoire (objects MemoryStream). J’essaie d’utiliser la bibliothèque DotNetZip. J’ai trouvé cet exemple: http://www.4guysfromrolla.com/articles/092910-1.aspx#postadlink . Mais Response.OutputStream n’est pas disponible dans l’API Web et cette technique ne fonctionne donc pas tout à fait. J’ai donc essayé de sauvegarder le fichier zip dans un nouveau MemoryStream; mais ça a jeté. Enfin, j’ai essayé d’utiliser PushStreamContent. Voici mon code:
public HttpResponseMessage Get(ssortingng 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 = ssortingng.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 = ssortingng.Format("{0}_images.zip", dzImages.Count()) }; return response; } }
zipFile.Save (memStream) renvoie une référence null. Mais ni zipFile ni memStream ne sont nuls et il n’y a pas d’exception interne. Donc, je ne suis pas sûr de ce qui cause la référence nulle. J’ai très peu d’expérience avec les API Web, les stream de mémoire et je n’avais jamais utilisé DotNetZipLibrary auparavant. Cette question fait suite à la question suivante: recherchez un contrôleur d’API Web ASP.NET efficace pouvant renvoyer de manière fiable des images JPEG de 30 à 50 ~ 3 Mo.
Des idées? Merci!
Une approche plus générique fonctionnerait comme ceci:
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}; }
La méthode ZipContentResult
peut également vivre dans une classe de base et être utilisée à partir de toute autre action de tout contrôleur api.
La classe PushStreamContent peut être utilisée dans ce cas pour éliminer le besoin de MemoryStream, au moins pour l’ensemble du fichier zip. Il peut être implémenté comme ceci:
public HttpResponseMessage Get(ssortingng 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 = ssortingng.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 = ssortingng.Format("{0}_images.zip", dzImages.Count()), }; var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = streamContent }; return response; }
Idéalement, il serait possible de créer cela encore plus dynamicment à l’aide de la classe ZipOutputStream afin de créer dynamicment le zip au lieu d’utiliser ZipFile. Dans ce cas, le MemoryStream de chaque bitmap ne serait pas nécessaire.
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 utilisé .NET 4.6.1 avec MVC 5
Je viens d’avoir le même problème que vous.
zipFile.Save(outputStream); //I got Null Reference Exception here.
Le problème était que j’ajoutais des fichiers à partir d’un stream de mémoire, comme ceci:
zip.AddEntry(fileName, ms);
Tout ce que vous avez à faire est de le changer en ceci:
zip.AddEntry(fileName, ms.ToArray());
Il semble que lorsque le rédacteur décide d’écrire le fichier et d’essayer de lire le stream, celui-ci est récupéré ou sth …
À votre santé!