JustAMartin
JustAMartin

Reputation: 13753

How to create a ZIP file from byte array using Microsoft.IO.RecyclableMemoryStream?

I need to save a byte array as a compressed ZIP file. As MemoryStream can grow large and cause out-of-memory exceptions on our shared Azure server instances, I'm using Microsoft.IO.RecyclableMemoryStream, which has saved me from out-of-memory exceptions in a few other cases.

However, it does not want to work with ZipArchive nicely.

The first attempt with a simple test example causes unfinalized archive that cannot be opened:

var data = Encoding.UTF8.GetBytes("This is a test string");

byte[] compressedBytes = default;

var manager = new RecyclableMemoryStreamManager();
using var originalStream = manager.GetStream("Raw", data);
using var compressedStream = manager.GetStream("Compressed");
using var zipArchive = new ZipArchive(compressedStream, ZipArchiveMode.Create, false);

var zipEntry = zipArchive.CreateEntry("test.txt");
using var zipEntryStream = zipEntry.Open();
originalStream.CopyTo(zipEntryStream);

// the following does not help, the archive is still corrupt
zipEntryStream.Flush();
zipEntryStream.Close();

compressedBytes = compressedStream.ToArray();

// the result - File cannot be opened: "Unexpected end of archive"
// BTW, I know this is not a good way to return bytes from a controller method; using just for testing the bytes directly
return File(compressedBytes, "application/zip", "test.zip");

It seems ZipArchive needs to be disposed to finalize the archive. So I tried this next:


using var originalStream = manager.GetStream("Raw", data);
using var compressedStream = manager.GetStream("Compressed");

// scoped `using` statement here
// to ensure the zip archive gets disposed early enough,
// otherwise it's not finalized; and Flush + Close does not help
using (var zipArchive = new ZipArchive(compressedStream, ZipArchiveMode.Create, false))
{
    var zipEntry = zipArchive.CreateEntry("test.txt");
    using var zipEntryStream = zipEntry.Open();
    originalStream.CopyTo(zipEntryStream);
}
// Exception: Cannot access a disposed object.:(
compressedBytes = compressedStream.ToArray();

This time the archive is finalized but bytes cannot be retrieved because ZipArchive has disposed the underlying RecyclableMemoryStream.

Only the classic approach with MemoryStream works fine:

var manager = new RecyclableMemoryStreamManager();
using var originalStream = manager.GetStream("Raw", data);
using var compressedStream = new MemoryStream();
// scoped `using` statement here
// to ensure the zip archive gets disposed early enough,
// otherwise it's not finalized; and Flush + Close does not help
using (var zipArchive = new ZipArchive(compressedStream, ZipArchiveMode.Create, false))
{
    var zipEntry = zipArchive.CreateEntry("test.txt");
    using var zipEntryStream = zipEntry.Open();
    originalStream.CopyTo(zipEntryStream);
}

compressedBytes = compressedStream.ToArray();

So, this seems to be one of those rare cases when RecyclableMemoryStream does not work well as a drop-in replacement for MemoryStream.

Is there any way to force ZipArchive to finalize the zip without disposing the underlying stream?

Or should I use a different tool instead of ZipArchive?

Upvotes: 1

Views: 168

Answers (0)

Related Questions