使用 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?

如何在 ASP.NET Core Web API 中使用 MassTransit 事件总线?

如何使 ASP.NET Web API 正确响应 403 或 401?