Используя ASP.NET Web API, как контроллер может вернуть коллекцию потоковых изображений, сжатых с использованием библиотеки DotNetZip?
Как создать контроллер веб-API, который генерирует и возвращает сжатый zip файл, потоковый из коллекции файлов JPEG в памяти (объекты MemoryStream). Я пытаюсь использовать DotNetZip Library. Я нашел этот пример: http://www.4guysfromrolla.com/articles/092910-1.aspx#postadlink. Но Response.OutputStream недоступен в веб-API и поэтому техническая работа не совсем работает. Поэтому я попытался сохранить zip файл в новый MemoryStream; но он бросил. Наконец, я попытался использовать PushStreamContent. Здесь мой код:
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) генерирует нулевую ссылку. Но ни zipFile, ни memStream не являются нулевыми, и нет внутреннего исключения. Поэтому я не уверен, что вызывает нулевую ссылку. У меня очень мало опыта работы с Web API, потоками памяти, и я никогда раньше не использовал DotNetZipLibrary. Это зависит от этого вопроса: Хотите эффективный контроллер веб-API ASP.NET, который может надежно вернуть от 30 до 50 ~ 3 МБ JPEG
Любые идеи? спасибо!
Ответы
Ответ 1
Более общий подход будет работать следующим образом:
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};
}
Метод ZipContentResult
также может работать в базовом классе и использоваться из любого другого действия в любом контроллере api.
Ответ 2
В этом случае можно использовать класс PushStreamContent, чтобы исключить необходимость в MemoryStream, по крайней мере, для всего zip файла. Он может быть реализован следующим образом:
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;
}
В идеале можно было бы сделать это еще более динамически созданным с помощью класса ZipOutputStream для динамического создания zip вместо использования ZipFile. В этом случае MemoryStream для каждого растрового изображения не понадобится.
Ответ 3
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;
}
Используемая среда .NET 4.6.1 с MVC 5
Ответ 4
У меня была такая же проблема, как у вас.
zipFile.Save(outputStream); //I got Null Reference Exception here.
Проблема заключалась в том, что я добавлял файлы из потока памяти следующим образом:
zip.AddEntry(fileName, ms);
Все, что вам нужно сделать, это изменить его на это:
zip.AddEntry(fileName, ms.ToArray());
Похоже, что когда автор решает записать файл и пытается прочитать поток, поток собирает мусор или sth...
Ура!