Creating a Zip Archive in Memory Using System.Io.Compression

Creating a ZIP archive in memory using System.IO.Compression

Thanks to ZipArchive creates invalid ZIP file, I got:

using (var memoryStream = new MemoryStream())
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
var demoFile = archive.CreateEntry("foo.txt");

using (var entryStream = demoFile.Open())
using (var streamWriter = new StreamWriter(entryStream))
{
streamWriter.Write("Bar!");
}
}

using (var fileStream = new FileStream(@"C:\Temp\test.zip", FileMode.Create))
{
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.CopyTo(fileStream);
}
}

That indicated we need to call Dispose on ZipArchive before we can use it, which as Amir suggests is likely because it writes final bytes like checksum to the archive that makes it complete. But in order not close the stream so we can re-use it after you need to pass true as the third parameter to ZipArchive.

How to create a zip file in memory, starting from file bytes?

Try to take a look to this: Creating a ZIP Archive in Memory Using System.IO.Compression. The solution is this:

using (var memoryStream = new MemoryStream())
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
var demoFile = archive.CreateEntry("foo.txt");

using (var entryStream = demoFile.Open())
using (var streamWriter = new StreamWriter(entryStream))
{
streamWriter.Write("Bar!");
}
}

using (var fileStream = new FileStream(@"C:\Temp\test.zip", FileMode.Create))
{
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.CopyTo(fileStream);
}
}

It explains how to create the zip archive in memory and contains a link to another useful article that explains the use of the leaveOpen argument to prevent the closing of the stream: ZipArchive creates invalid ZIP file that contains this solution:

using (MemoryStream zipStream = new MemoryStream())
{
using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
{
var entry = zip.CreateEntry("test.txt");
using (StreamWriter sw = new StreamWriter(entry.Open()))
{
sw.WriteLine(
"Etiam eros nunc, hendrerit nec malesuada vitae, pretium at ligula.");
}
}

var file = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync(
"test.zip",
CreationCollisionOption.ReplaceExisting);

zipStream.Position = 0;
using (Stream s = await file.OpenStreamForWriteAsync())
{
zipStream.CopyTo(s);
}
}

I hope it's helpful!

EDIT

Instead of zipArchiveMemoryStream.GetBuffer() use zipArchiveMemoryStream.ToArray()

C# create zip file using zip archive System.IO.Compression

You don't close the stream, you open with 'manifest.Open()'. Then it might not have written everything to the zip.

Wrap it in another using, like this:

using (var stream = new FileStream(path, FileMode.Create))
{
using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, true))
{
ZipArchiveEntry manifest = archive.CreateEntry(filenameManifest);
using (Stream st = manifest.Open())
{
using (StreamWriter writerManifest = new StreamWriter(st))
{
writerManifest.WriteLine(JSONObject_String);
}
}

ZipArchiveEntry pdfFile = archive.CreateEntry(filenameManifest);
using (Stream st = manifest.Open())
{
using (StreamWriter writerPDF = new StreamWriter(st))
{
writerPDF.WriteLine(pdf);
}
}
}
}

How to create ZipArchive from files in memory in C#?

Yes, you can do this, using the ZipArchive.CreateEntry method, as @AngeloReis pointed out in the comments, and described here for a slightly different problem.

Your code would then look like this:

using (var ms = new MemoryStream())
{
using (var zipArchive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var attachment in attachmentFiles)
{
var entry = zipArchive.CreateEntry(attachment.FileName, CompressionLevel.Fastest);
using (var entryStream = entry.Open())
{
attachment.InputStream.CopyTo(entryStream);
}
}
}
...
}

C# ZipArchive - How to nest internal .zip files without writing to disk

So I created a FileStream instead of a MemoryStream so the code can be tested easier

public static Stream CreateOuterZip()
{
string fileContents = "Lorem ipsum dolor sit amet";

// Final zip file
var fs = new FileStream(
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SendToClient.zip"), FileMode.OpenOrCreate);

// Create inner zip 1
var innerZip1 = new MemoryStream();
using (var archive = new ZipArchive(innerZip1, ZipArchiveMode.Create, true))
{
var file1 = archive.CreateEntry("File1.xml");
using (var writer = new BinaryWriter(file1.Open()))
{
writer.Write(fileContents); // Change fileContents to real XML content
}
var file2 = archive.CreateEntry("File2.xml");
using (var writer = new BinaryWriter(file2.Open()))
{
writer.Write(fileContents); // Change fileContents to real XML content
}
}

// Create inner zip 2
var innerZip2 = new MemoryStream();
using (var archive = new ZipArchive(innerZip2, ZipArchiveMode.Create, true))
{
var file3 = archive.CreateEntry("File3.xml");
using (var writer = new BinaryWriter(file3.Open()))
{
writer.Write(fileContents); // Change fileContents to real XML content
}
var file4 = archive.CreateEntry("File4.xml");
using (var writer = new BinaryWriter(file4.Open()))
{
writer.Write(fileContents); // Change fileContents to real XML content
}
}

using (var archive = new ZipArchive(fs, ZipArchiveMode.Create, true))
{
// Create inner zip 1
var innerZipEntry = archive.CreateEntry("InnerZip1.zip");
innerZip1.Position = 0;
using (var s = innerZipEntry.Open())
{
innerZip1.WriteTo(s);
}

// Create inner zip 2
var innerZipEntry2 = archive.CreateEntry("InnerZip2.zip");
innerZip2.Position = 0;
using (var s = innerZipEntry2.Open())
{
innerZip2.WriteTo(s);
}
}

fs.Close();
return fs; // The file is written, can probably just close this
}

You can obviously modify this method to return a MemoryStream, or change the method to Void to just have the zip file written out to disk

System.IO.Compression.ZipArchive memory management

The code you've written is causing the ZipArchive class to write a whole new archive at the end of your previous one, which of course corrupts the file.

The way to do what you want is to copy the original archive to a new file as you create it, and then replace the original with the new one. For example:

string tempFile = Path.GetTempFileName();

using (ZipArchive original =
new ZipArchive(File.Open(archiveFileStream, FileMode.Open), ZipArchiveMode.Read))
using (ZipArchive newArchive =
new ZipArchive(File.Open(tempFile, FileMode.Create), ZipArchiveMode.Create))
{
foreach (ZipArchiveEntry entry in original.Entries)
{
ZipArchiveEntry newEntry = newArchive.Create(entry.FullName);

using (Stream source = entry.Open())
using (Stream destination = newEntry.Open())
{
source.CopyTo(destination);
}
}

for (int directoryGroupFileIndex = 0;
directoryGroupFileIndex < directoryGroup.Length;
directoryGroupFileIndex++)
{
FileInfo file = directoryGroup[directoryGroupFileIndex];
String archiveEntryName = file.Name;
String archiveEntryPath = DateTime.Now.ToString("yyyy-MM-dd");
String archiveEntryFullName = Path.Combine(archiveEntryPath, archiveEntryName);

ZipArchiveEntry archiveEntry = newArchive.CreateEntryFromFile(
file.FullName, archiveEntryFullName, CompressionLevel.Optimal);
}
}

File.Delete(archiveFileStream);
File.Move(tempFile, archiveFileStream);

Note that this isn't actually going to be slower than ZipArchiveMode.Update. When you use the update mode, the ZipArchive class reads the entire archive into memory (as you noted), and then when you close it, it recompresses and writes everything back out.

The above does basically the exact same computations, but simply uses the disk as the intermediate storage instead of memory.

Create Zip archive from multiple in memory files in C#

Use ZipEntry and PutNextEntry() for this. The following shows how to do it for a file, but for an in-memory object just use a MemoryStream

FileStream fZip = File.Create(compressedOutputFile);
ZipOutputStream zipOStream = new ZipOutputStream(fZip);
foreach (FileInfo fi in allfiles)
{
ZipEntry entry = new ZipEntry((fi.Name));
zipOStream.PutNextEntry(entry);
FileStream fs = File.OpenRead(fi.FullName);
try
{
byte[] transferBuffer[1024];
do
{
bytesRead = fs.Read(transferBuffer, 0, transferBuffer.Length);
zipOStream.Write(transferBuffer, 0, bytesRead);
}
while (bytesRead > 0);
}
finally
{
fs.Close();
}
}
zipOStream.Finish();
zipOStream.Close();

Zip files without inclusion of folders

You seem to be under the impression that the file will be added to the archive, which is not the case. CreateEntry merely creates an entry with the specified path and entry name, you still need to write the actual file.

In fact, the code in your question is quite similar to the code in the documentation, so I assume you got it from there?

Anyway, to get only the file name you can use Path.GetFileName and then you can write the actual file content to the zip entry.

var filePath = @"C:\temp\foo.txt";
var zipName = @"C:\temp\foo.zip";

using (FileStream zipToOpen = new FileStream(zipName, FileMode.CreateNew))
{
using (ZipArchive archive = new ZipArchive(zipToOpen, ZipArchiveMode.Update))
{
ZipArchiveEntry readmeEntry = archive.CreateEntry(Path.GetFileName(filePath));
using (StreamReader reader = new StreamReader(filePath))
using (StreamWriter writer = new StreamWriter(readmeEntry.Open()))
{
writer.Write(reader.ReadToEnd());
}
}
}

The code above will create an archive with foo.txt in the root and with the content of the source file, without any additional directories.



Related Topics



Leave a reply



Submit