Creating Zip Archive in memory, and returning it f

2019-04-14 15:27发布

问题:

So i am trying to create a zip archive and return it from my web api. The controller is called from an angular 2 site. Currently the zip file is created but when i open it i get an invalid message. Originally i had the streams in using statements but had to change that as they were being disposed before the request had completed.

What i need is to create the zip file, add the csv file to its content. And then return the zip file. But the zip file is always invalid. I have read the zip archive needs to be disposed in order for it write its contents, however im not sure what is the best way to implement this. Thank you for any guidance.

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);
        }
    }

In Response to using statements:

At one point i had everything wrapped in using statements but the response would fail because the stream had already been disposed. You can see this below.

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);
        }
    }

回答1:

You are missing a handful of using(){} blocks.

Make sure you close the originalFileSteam, zipEntryStream and the zipArchive in the proper order.

And just to be sure, reset the memoryStream. I don't know if this is needed but it won't hurt.

//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);


回答2:

The memory stream is being disposed when the zip archive is disposed.

You should dispose of the archive to force it to write its content to its underlying memory stream, but take note of the following

ZipArchive.Dispose()

Unless you construct the object by using the ZipArchive(Stream, ZipArchiveMode, Boolean) constructor overload and set its leaveOpen parameter to true, all underlying streams are closed and no longer available for subsequent write operations.

When you are finished using this instance of ZipArchive, call Dispose() to release all resources used by this instance. You should eliminate further references to this ZipArchive instance so that the garbage collector can reclaim the memory of the instance instead of keeping it alive for finalization.

And since you want to continue using the memory stream then you need to make sure it remains open and that the stream pointer is reset so that is can be read from the beginning.

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);
    }
}