Reputation: 13753
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