在内存中创建Zip压缩文件,并从Web API返回

4
我正在尝试创建一个zip归档文件并从我的Web API中返回它。该控制器从一个Angular 2网站调用。目前,zip文件已经被创建,但是当我打开它时,会出现无效的消息。最初,我在using语句中使用了流,但不得不更改,因为它们在请求完成之前就被销毁了。
我需要做的是创建zip文件,将csv文件添加到其内容中,然后返回zip文件。但是zip文件始终无效。我已经阅读过zip存档需要被释放才能写入其内容,但我不确定最佳实现方法是什么。非常感谢任何指导。
public async Task<IHttpActionResult> ExportReport(int id, ReportModel report)
    {
        try
        {
            var result = await ReportGenerationService.ExportReportForId(id, report.Page, report.PageSize, report.SortField, report.SortDir, report.SearchTerm, report.StartDate, report.EndDate, report.UserId, report.TeamId, report.SelectedDateItem);
            if (result != null)
            {

                var compressedFileStream = new MemoryStream();

                var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Update, false);

                var zipEntry = zipArchive.CreateEntry("textExport.csv");
                var origionalFileSteam = new MemoryStream(result.ExportToBytes());
                var zipEntryStream = zipEntry.Open();
                origionalFileSteam.CopyTo(zipEntryStream);


                var response = new HttpResponseMessage(HttpStatusCode.OK) {Content = new StreamContent(compressedFileStream)};
                response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
                response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
                {
                    FileName = "Export.zip"
                };

                var t = compressedFileStream.CanRead;
                return ResponseMessage(response);

            }

            return NotFound();
        }
        catch (Exception ex)
        {
            return InternalServerError(ex);
        }
    }

关于使用using语句的回应:

曾经有一次,我把所有内容都包含在using语句中,但响应失败了,因为流已被释放。您可以在下面看到这种情况。

public async Task<IHttpActionResult> ExportReport(int id, ReportModel report)
    {
        try
        {
            var result = await ReportGenerationService.ExportReportForId(id, report.Page, report.PageSize, report.SortField, report.SortDir, report.SearchTerm, report.StartDate, report.EndDate, report.UserId, report.TeamId, report.SelectedDateItem);
            if (result != null)
            {

                var compressedFileStream = new MemoryStream();
                var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Update, false);

                        //Create a zip entry for each attachment
                    var zipEntry = zipArchive.CreateEntry("textExport.csv");

                    //Get the stream of the attachment
                    using (var originalFileStream = new MemoryStream(result.ExportToBytes()))
                    using (var zipEntryStream = zipEntry.Open()) {
                        //Copy the attachment stream to the zip entry stream
                        originalFileStream.CopyTo(zipEntryStream);
                    }
                    compressedFileStream.Position = 0;

                    var response = new HttpResponseMessage(HttpStatusCode.OK) {Content = new StreamContent(compressedFileStream)};
                    response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
                    response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
                    {
                        FileName = "Export.zip"
                    };

                    return ResponseMessage(response);
            }

            return NotFound();
        }
        catch (Exception ex)
        {
            return InternalServerError(ex);
        }
    }

你能否使用浏览器调用 Web API 方法,并将结果保存到文件中?然后查看它是否是合法或非法文件...也就是说,将 Angular 排除在外。 - granadaCoder
2个回答

7

您缺少一些 using(){} 块。

请确保按正确顺序关闭 originalFileSteam、zipEntryStream 和 zipArchive。

为了确保,重置 memoryStream。我不知道是否需要,但这样做不会有害。

//using (var compressedFileStream = new MemoryStream())
var compressedFileStream = new MemoryStream();

using (var zipArchive = new ZipArchive(...)) 
{
        //Create a zip entry for each attachment
        var zipEntry = zipArchive.CreateEntry("textExport.csv");

        //Get the stream of the attachment
        using (var originalFileStream = new MemoryStream(result.ExportToBytes()))
        using (var zipEntryStream = zipEntry.Open()) 
        {
            //Copy the attachment stream to the zip entry stream
            originalFileStream.CopyTo(zipEntryStream);
        }
}

//compressedFileStream .Position = 0;
var responseBytes =new MemoryStream(compressedFileStream.ToArray());

var response = new HttpResponseMessage(HttpStatusCode.OK) {Content = new StreamContent(responseBytes )};
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

return ResponseMessage(response);

我已经在问题中添加了using语句。之前我尝试过,但是由于流已在发送响应时被释放,所以出现了更多的错误。 - Tony_89
当zipArchive关闭时(??你确定不是这样吗?),然后从旧缓冲区重新打开一个新流。 MemStream.Buffer仍然可以访问。 - bommelding
1
我已经把它们放进去了,但压缩文件返回仍然无效。 - Tony_89
现在已经搞定了,感谢您在此方面的帮助,您所做的更改与保持ZipArchive打开的其他答案相结合效果很好。 - Tony_89
@Nkosi - 糟糕的缩进是有原因的。 - bommelding
显示剩余10条评论

7

在压缩存档被处理时,内存流也会被释放。

你应该释放存档以强制其将内容写入其底层内存流,但请注意以下内容:

ZipArchive.Dispose()

除非你使用ZipArchive(Stream, ZipArchiveMode, Boolean)构造函数重载并将其leaveOpen参数设置为true,否则所有底层流都会关闭并且不再可用于后续的写操作。

当你完成对此ZipArchive实例的使用时,请调用Dispose()以释放此实例使用的所有资源。你应该消除对此ZipArchive实例的进一步引用,以便垃圾收集器可以回收实例的内存,而不是将其保持活动状态以进行最终处理。

既然您想继续使用内存流,那么您需要确保它仍然处于打开状态,并且流指针已重置,以便可以从开头读取。

public async Task<IHttpActionResult> ExportReport(int id, ReportModel report) {
    try {
        var result = await ReportGenerationService.ExportReportForId(id, report.Page, report.PageSize, report.SortField, report.SortDir, report.SearchTerm, report.StartDate, report.EndDate, report.UserId, report.TeamId, report.SelectedDateItem);
        if (result != null) {
            var compressedFileStream = new MemoryStream();
            using(var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create, 
                leaveOpen: true)) { //<--This is important to keep stream open
                //Create a zip entry for each attachment
                var zipEntry = zipArchive.CreateEntry("textExport.csv");
                //Get the stream of the attachment
                using (var originalFileStream = new MemoryStream(result.ExportToBytes()))
                using (var zipEntryStream = zipEntry.Open()) {
                    //Copy the attachment stream to the zip entry stream
                    await originalFileStream.CopyToAsync(zipEntryStream);
                }
            }// disposal of archive will force data to be written/flushed to memory stream.
            compressedFileStream.Position = 0;//reset memory stream position.
            
            var response = new HttpResponseMessage(HttpStatusCode.OK) {
                Content = new StreamContent(compressedFileStream)
            };
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") {
                FileName = "Export.zip"
            };
            return ResponseMessage(response);
        }
        return NotFound();
    } catch (Exception ex) {
        return InternalServerError(ex);
    }
}

谢谢您的帮助,我希望能将两个答案都标记为正确。保持ziparchive打开状态确实有很大帮助。再次感谢您。 - Tony_89
@Tony_89 不用担心。另一个答案非常有价值,已经帮助你完成了大部分工作。 - Nkosi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接