3

The question is how can I create a zipped (compressed) folder on the fly using ASP.NET Core 2 (currently) Web API?

I am using System.IO.Compression.ZipArchive

I have been several blog posts where this is done using streams or byte arrays, all give me the same output.

I am able to download the zip folder but unable to open it.

The zipped folder is the correct size. Even though it doesn't open.

They way I want it to work is for a user to click a button which runs this action and return a zipped folder containing one or multiple files.

[HttpGet]
[Route("/api/download/zip")]
public async Task<IActionResult> Zip()
{
    byte[] bytes = null;

    using (MemoryStream zipStream = new MemoryStream())
    using (var zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
    {
        var tempFileName = await _azure.GetFilesByRef("Azure_FilePath");

        // Running just this line gives me the zipped folder (empty) which I can open
        ZipArchiveEntry entry = zip.CreateEntry("File1.pdf", CompressionLevel.Fastest);

        // Adding this 2nd section will download the zip but will not open the zip folder
        using (Stream stream = entry.Open())
        using (FileStream fs = new FileStream(tempFileName, FileMode.Open, FileAccess.Read))
        {
            await fs.CopyToAsync(stream);
        }

        bytes = zipStream.ToArray();
    }

    return File(bytes, MediaTypeNames.Application.Zip, $"Attachments{DateTime.Now.ToBinary()}.zip");
}

Can anyone spot a mistake or suggest an alternative solution?

Nkosi
  • 235,767
  • 35
  • 427
  • 472
shammelburg
  • 6,974
  • 7
  • 26
  • 34
  • 1
    Does it work if you send the file from azure directly? Without zipping? Does it work with a local file that’s not coming from Azure? Does the file size of the zip make sense? – poke May 24 '18 at 17:53
  • It is not clear whether you are trying to create a zip and sent it down the pipe or if you are trying to get a zip from azure and pass it along. – Nkosi May 24 '18 at 17:55
  • You have to add an entry for each item you want added to the archive. – Nkosi May 24 '18 at 18:10
  • @poke yes, I can download the file from azure directly, this is not the functionality I want though. – shammelburg May 24 '18 at 18:10
  • I was talking about modifying your code incrementally, so that you can figure out what the problem is. – poke May 24 '18 at 18:15

1 Answers1

7

All the data is not written to the stream until the archive is disposed. So in this case, the data in the stream may be incomplete if the archive has not flushed it as yet.

memoryStream.ToArray is being called before the archive has a chance to flush all its data to the underlying stream.

Consider refactoring to

//...

var tempFileName = await _azure.GetFilesByRef("Azure_FilePath");
using (MemoryStream zipStream = new MemoryStream()) {
    using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Create, leaveOpen: true)) {            
        ZipArchiveEntry entry = archive.CreateEntry("File1.pdf", CompressionLevel.Fastest);    
        using (Stream stream = entry.Open())
        using (FileStream fs = new FileStream(tempFileName, FileMode.Open, FileAccess.Read)) {
            await fs.CopyToAsync(stream);
        }
    }// disposal of archive will force data to be written to memory stream.
    zipStream.Position = 0; //reset memory stream position.
    bytes = zipStream.ToArray(); //get all flushed data
}

//...

The assumption in your example is also that the FileStream opened is the correct file type of the created entry; A single PDF file. Otherwise consider extracting a name from the tempFileName.

You have to add a unique entry (file path) for each item you want added to the archive.

Nkosi
  • 235,767
  • 35
  • 427
  • 472