使用 ASP.NET Web API,控制器如何返回使用 DotNetZip 库压缩的流图像集合?
Posted
技术标签:
【中文标题】使用 ASP.NET Web API,控制器如何返回使用 DotNetZip 库压缩的流图像集合?【英文标题】:Using ASP.NET Web API, how can a controller return a collection of streamed images compressed using DotNetZip Library? 【发布时间】:2013-01-12 11:49:34 【问题描述】:如何创建一个 Web API 控制器,该控制器生成并返回从内存中 JPEG 文件(MemoryStream 对象)集合流式传输的压缩 zip 文件。我正在尝试使用 DotNetZip 库。我找到了这个例子:http://www.4guysfromrolla.com/articles/092910-1.aspx#postadlink。但是 Response.OutputStream 在 Web 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 都不是 null 并且没有内部异常。所以我不确定是什么导致了空引用。我对 Web API、内存流的经验很少,而且我以前从未使用过 DotNetZipLibrary。这是对这个问题的跟进:Want an efficient ASP.NET Web API controller that can reliably return 30 to 50 ~3MB JPEGs
有什么想法吗?谢谢!
【问题讨论】:
我没有发现您的 WebApi 代码有任何问题。但是我会使用 StreamContent 而不是 ByteArrayContent。我意识到你说你在 zipFile.Save 中获得了空引用,但我会尝试删除 new ZipFile() 周围的 using()。 谢谢。不幸的是,删除 using 并没有帮助。它仍然在 zipFile.Save(...) 处抛出 NullReferenceException。这是跟踪:在 Ionic.Zlib.ParallelDeflateOutputStream.Close() 的 Ionic.Zlib.ParallelDeflateOutputStream._Flush(Boolean lastInput) 在 Ionic.Zip.ZipEntry.FinishOutputStream(Stream s, CountingStream entryCounter, 流加密器, 流压缩器, CrcCalculatorStream 输出)在 Ionic.Zip.ZipEntry._WriteEntryData(Stream s) 在 Ionic.Zip.ZipEntry.Write(Stream s) 在 Ionic.Zip.ZipFile.Save() 在 Ionic.Zip.ZipFile.Save(Stream outputStream) 做了bitmap.Save后,memoryStream的位置是什么?您可能需要设置 memoryStream.Position = 0 否则您可能会将零字节存储到 zip 文件中。 所以它不会通过 bitmap.Save()。那就是它抛出的地方。调用 Save 时 MemoryStream 的位置为零。正如我上面提到的,我没有太多使用 MemoryStream 对象的经验。而且我还没有时间阅读它们。我想知道是否需要在流中预先分配空间。 我以为你说过它是 zipFile.Save(...) 抛出的地方。您不需要为内存流预先分配任何东西。你使用它们的方式很好。我只是觉得你在bitmap.Save(memoryStream, ImageFormat.Jpeg);
之后缺少memoryStream.Position = 0;
【参考方案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://***.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 控制器中的任何其他操作中使用。
【讨论】:
效果很好。不知道为什么这没有引起注意 @Felix Alcala 此代码运行良好,但我的 zip 已损坏,知道为什么吗? @user3378165 不幸的是没有。自从我发布它以来,它一直在我们的服务器上运行,没有任何问题。然而,C#-zip-handling 总是有些痛苦。一些起点可能是:下载是否被截断?也许文件名包含非美国字符并且可能需要特别考虑?你能找到更多关于损坏的信息吗(例如在 Windows 资源管理器中工作,但不是在 7zip 中工作)?在 mono/Xamarin 中打开 zip 时,需要设置特殊的构建设置等。【参考方案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());
似乎当作者决定实际写入文件并尝试读取流时,流被垃圾收集或......
干杯!
【讨论】:
以上是关于使用 ASP.NET Web API,控制器如何返回使用 DotNetZip 库压缩的流图像集合?的主要内容,如果未能解决你的问题,请参考以下文章
如何从 ASP.NET MVC 到 ASP.NET Core Web API 的 PUT?
ASP.NEt MVC 使用 Web API 返回 Razor 视图
Web API系列教程2.1 — ASP.NET Web API中的路由机制
如何将 Web API 控制器添加到现有的 ASP.NET Core MVC?