I have a ZipArchive object which contains an XML file that I am modifying. I then want to return the modified ZipArchive.
Here's the code I have:
var package = File.ReadAllBytes(/* location of existing .zip */);
using (var packageStream = new MemoryStream(package, true))
using (var zipPackage = new ZipArchive(packageStream, ZipArchiveMode.Update))
{
// obtain the specific entry
var myEntry = zipPackage.Entries.FirstOrDefault(entry => /* code elided */));
XElement xContents;
using (var reader = new StreamReader(myEntry.Open()))
{
// read the contents of the myEntry XML file
// then modify the contents into xContents
}
using (var writer = new StreamWriter(myEntry.Open()))
{
writer.Write(xContents.ToString());
}
return packageStream.ToArray();
}
This code throws a "Memory stream is not expandable" exception on the packageStream.ToArray()
call.
Can anyone explain what I've done wrongly, and what is the correct way of updating an existing file inside a ZipArchive?
Clearly, ZipArchive
wants to expand or resize the ZIP archive stream. However, you have provided a MemoryStream
with a fixed stream length (due to using the constructor MemoryStream(byte[], bool)
, which creates a memory stream with a fixed length that is equal to the length of the array provided to the constructor).
Since ZipArchive
wants to expand (or resize) the stream, provide an resizable MemoryStream
(using its parameter-less constructor). Then copy the original file data into this MemoryStream and proceed with the ZIP archive manipulations.
And don't forget to reset the MemoryStream read/write position back to 0
after copying the original file data into it, otherwise ZipArchive will only see "End of Stream" when trying to read the ZIP archive data from this stream.
using (var packageStream = new MemoryStream())
{
using (var fs = File.OpenRead(/* location of existing .zip */))
{
fs.CopyTo(packageStream);
}
packageStream.Position = 0;
using (var zipPackage = new ZipArchive(packageStream, ZipArchiveMode.Update))
{
... do your thing ...
}
return packageStream.ToArray();
}
This code here contains one more correction. In the original code in the question, return packageStream.ToArray();
has been placed within the using
block of the ZipArchive. At the time this line will be executed, the ZipArchive instance might not yet have written all data to the MemoryStream, perhaps keeping some data still in some internal buffers and/or perhaps having deferred writing some ZIP data structures.
To ensure that the ZipArchive has actually written all necessary data completely to the MemoryStream, it is here sufficient to move return packageStream.ToArray();
outside after the ZipArchive using
block. At the end of its using
block, the ZipArchive will be disposed which will also ensure that ZipArchive has written all so far yet unwritten data to the stream. Thus, accessing the MemoryStream after the ZipArchive has been disposed off will yield the complete data of the completely updated ZIP archive.
Side note: Do this only with small-ish ZIP files. The MemoryStream will obviously use internal data buffers (arrays) to hold the data in the MemoryStream. However, packageStream.ToArray();
will create a copy of the data in the MemoryStream, so for a period of time the memory requirements of this routine will be a little more than twice the size of the ZIP archive.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With