C# Create ZIP Archive with multiple files - c#

I'm trying to create a ZIP archive with multiple text files as follows:
Dictionary<string, string> Values = new Dictionary<string, string>();
using (var memoryStream = new MemoryStream())
{
string zip = #"C:\Temp\ZipFile.zip";
foreach (var item in Values)
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
var file = archive.CreateEntry(item.Key + ".txt");
using (var entryStream = file.Open())
using (var streamWriter = new StreamWriter(entryStream))
{
streamWriter.Write(item.Value);
}
}
}
using (var fileStream = new FileStream(zip, FileMode.Create))
{
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.CopyTo(fileStream);
}
}
However, the ZIP is created with only the last text file, what's wrong?

You are creating ZipArchive on each iteration. Swapping foreach and using should solve it:
Dictionary<string, string> Values = new Dictionary<string, string>();
using (var memoryStream = new MemoryStream())
{
string zip = #"C:\Temp\ZipFile.zip";
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
foreach (var item in Values)
{
var file = archive.CreateEntry(item.Key + ".txt");
using (var entryStream = file.Open())
using (var streamWriter = new StreamWriter(entryStream))
{
streamWriter.Write(item.Value);
}
}
}
using (var fileStream = new FileStream(zip, FileMode.Create))
{
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.CopyTo(fileStream);
}
}

Each time your foreach loop runs it has the ZipArchiveMode as Create. That should be the problem, so it generates new zip everytime with new content on it, such as the last text file. Create an exception for each loop run after the first one it should be solved.

Related

How to create a zip archive with files and attach it to a zip archive?

I have a number of files that need to be grouped into archives, and all these archives need to be put into the archive.
using (var memoryStream = new MemoryStream())
{
foreach (var (day, _messages) in messages.GroupBy(x => x.Date).ToDictionary(x => x.Key, x => x.ToList()))
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
using (var archiveByDay = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
foreach (var message in _messages)
{
var fileInArchive = archiveByDay.CreateEntry($"{message.FileName}", CompressionLevel.Fastest);
var renderer = XmlHelper.SerializeWin1251(message);
using (var entryStream = fileInArchive.Open())
{
using (var fileToCompressStream = new MemoryStream(renderer.ToArray()))
{
fileToCompressStream.CopyTo(entryStream);
}
}
}
}
///How add archiveByDay to archive
}
}
}
At this point i'm stuck
(Untested code, basically just to demonstrate the idea behind it)
Try the following:
//create the parent archive at the top of the loop
//with the given code it would have been just 1 archive in 1 archive
//now it can be many archives in 1 archive
//write the archive to file system instead of in memory
using (var archive = new ZipArchive(File.Create("C:\\test\\test.zip"), ZipArchiveMode.Create, true))
{
foreach (var (day, _messages) in messages.GroupBy(x => x.Date).ToDictionary(x => x.Key, x => x.ToList()))
{
//move the memorystrem to the inner loop
using (var memoryStream = new MemoryStream())
{
using (var archiveByDay = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
foreach (var message in _messages)
{
var fileInArchive = archiveByDay.CreateEntry($"{message.FileName}", CompressionLevel.Fastest);
var renderer = XmlHelper.SerializeWin1251(message);
using (var entryStream = fileInArchive.Open())
using (var fileToCompressStream = new MemoryStream(renderer.ToArray()))
fileToCompressStream.CopyTo(entryStream);
}
}
//flush and set position to 0 enable reading the content that got written to the memory stream
memoryStream.Flush();
memoryStream.Position = 0;
//createentry for the parent archive and write the memory stream to it
var fileInParentArchive = archive.CreateEntry($"{Guid.NewGuid():D}", CompressionLevel.Fastest);
using (var entryStream = fileInParentArchive.Open())
using (var fileToCompressStream = new MemoryStream(memoryStream.ToArray()))
fileToCompressStream.CopyTo(entryStream);
}
}
}

Moving file directly into zip file

I'm trying move Source_File.mp4 into Destination_Zip.zip directly. I'm currently trying it by creating a new entry Transfer.mp4 and copying the bytes over. The code runs to the end but the file is never added to the ZIP file. I'm not sure if I'm missing something or if this isn't possible.
string sourceFile = basePath + "Source_File.mp4";
string destinationZip = basePath + "Desintation_Zip.zip";
using (var file = File.OpenRead(destinationZip))
{
MemoryStream memoryStream = new MemoryStream();
memoryStream.SetLength(file.Length);
file.Read(memoryStream.GetBuffer(), 0, (int)file.Length);
using (var zip = new ZipArchive(memoryStream, ZipArchiveMode.Update))
{
var entry = zip.CreateEntry("Transfer.mp4");
using (var destinationStream = entry.Open())
using (var sourceStream = File.OpenRead(sourceFile))
{
sourceStream.CopyTo(destinationStream);
}
}
}
My guess is that even though you've read the file and altered it, you didn't write anything back, i.e. changes persisted to MemoryStream, and didn't go anywhere after that.
You could try this (assumes you're using System.IO.Compression.ZipFile):
using (var zip = ZipFile.Open(destinationZip, ZipArchiveMode.Update))
{
var entry = zip.CreateEntry("Transfer.mp4");
using (var destinationStream = entry.Open())
using (var sourceStream = File.OpenRead(sourceFile))
{
sourceStream.CopyTo(destinationStream);
}
}
Or, if you're using ICSharpCode.SharpZipLib.Zip.ZipFile, do this:
using (var fileStream = new FileStream(destinationZip, FileMode.Open))
using (var zip = new ZipArchive(fileStream, ZipArchiveMode.Update))
{
var entry = zip.CreateEntry("Transfer.mp4");
using (var destinationStream = entry.Open())
using (var sourceStream = File.OpenRead(sourceFile))
{
sourceStream.CopyTo(destinationStream);
}
}
I'm still not sure why my initial code did not work, but I got it working by doing this instead:
string sourceFile = basePath + "Source_File.mp4";
string destinationZip = basePath + "Desintation_Zip.zip";
using (var destinationZipFileStream = new FileStream(destinationZip, FileMode.Open))
using (var destinationZipArchive = new ZipArchive(destinationZipFileStream, ZipArchiveMode.Update))
{
ZipArchiveEntry destinationWriter = destinationZipArchive.CreateEntry("Transfer.mp4");
using (var writer = new StreamWriter(destinationWriter.Open()))
using (var sourceStream = File.OpenRead(sourceFile))
{
sourceStream.CopyTo(writer.BaseStream);
}
}
Note: you will need to double check to make sure the file doesn't exist already or else you'll make duplicate files this way.

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

I need to create a zip file in memory, then send the zip file to the client. However, there are cases where the created zip file will need to contain other zip files that were also generated in memory. For instance, the file structure might look like this:
SendToClient.zip
InnerZip1.zip
File1.xml
File2.xml
InnerZip2.zip
File3.xml
File4.xml
I've been attempting to use the System.IO.Compression.ZipArchive library. I cannot use the System.IO.Compression.ZipFile library because my project's version of .NET is not compatible with it.
Here's an example of what I've tried.
public Stream GetMemoryStream() {
var memoryStream = new MemoryStream();
string fileContents = "Lorem ipsum dolor sit amet";
string entryName = "Lorem.txt";
string innerZipName = "InnerZip.zip";
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) {
ZipArchiveEntry entry = archive.CreateEntry(Path.Combine(innerZipName, entryName), CompressionLevel.Optimal);
using (var writer = new StreamWriter(entry.Open())) {
writer.Write(fileContents);
}
}
return memoryStream
}
However, this just puts Lorem.txt in a folder called "Inner.zip" (instead of in an actual zip file).
I can create an empty inner zip file if I create an entry called "Inner.zip" without writing to it. I can't add anything to it, though, and writing to an entry called "Inner.zip\Lorem.txt" afterward just creates a folder again (alongside the identically named empty .zip file).
I've also tried creating a separate archive, serializing it with a memory stream, then writing that to the original archive as a .zip.
public Stream CreateOuterZip() {
var memoryStream = new MemoryStream();
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) {
ZipArchiveEntry entry = archive.CreateEntry("Outer.zip", CompressionLevel.NoCompression);
using (var writer = new BinaryWriter(entry.Open())) {
writer.Write(GetMemoryStream().ToArray());
}
}
return memoryStream;
}
This just creates an invalid .zip file that windows doesn't know how to open, though.
Thanks in advance!
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
You should create ZipArchive for internal zip file also. Write it to stream (memorystream). And after write this stream as general stream into main zip.
static Stream Inner() {
var memoryStream = new MemoryStream();
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true)) {
var demoFile = archive.CreateEntry("foo2.txt");
using (var entryStream = demoFile.Open())
using (var streamWriter = new StreamWriter(entryStream)) {
streamWriter.Write("Bar2!");
}
}
return memoryStream;
}
static void Main(string[] args) {
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!");
}
var zip = archive.CreateEntry("inner.zip");
using (var entryStream = zip.Open()) {
var inner = Inner();
inner.Seek(0, SeekOrigin.Begin);
inner.CopyTo(entryStream);
}
}
using (var fileStream = new FileStream(#"d:\test.zip", FileMode.Create)) {
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.CopyTo(fileStream);
}
}
Thanks to this answer.

Re-create zip file from another zip file

Given a zip file, I need to re-create it with a specified compression level (eg, no compression).
I'm nearly there, but get the error:
Failed: Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory.
If I save the recreated zip file to windows, it looks like it's correct (correct file size, entries all exist with correct file sizes) but none of the files are extractable.
public static byte[] ReCompress(byte[] originalArchive, CompressionLevel newCompressionLevel)
{
var entries = new Dictionary<string, byte[]>();
///////////////////////////
// STEP 1: EXTRACT ALL FILES
///////////////////////////
using (var ms = new MemoryStream(originalArchive))
using (var originalZip = new ZipArchive(ms, ZipArchiveMode.Read))
{
foreach (var entry in originalZip.Entries)
{
var isFolder = entry.FullName.EndsWith("/");
if (!isFolder)
{
using (var stream = entry.Open())
using (var entryMS = new MemoryStream())
{
stream.CopyTo(entryMS);
entries.Add(entry.FullName, entryMS.ToArray());
}
}
else
{
entries.Add(entry.FullName, new byte[0]);
}
}
}
///////////////////////////
// STEP 2: BUILD ZIP FILE
///////////////////////////
using (var ms = new MemoryStream())
using (var newArchive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var uncompressedEntry in entries)
{
var newEntry = newArchive.CreateEntry(uncompressedEntry.Key, newCompressionLevel);
using (var entryStream = newEntry.Open())
using (var writer = new BinaryWriter(entryStream, Encoding.UTF8))
{
writer.Write(uncompressedEntry.Value);
}
}
return ms.ToArray();
}
}
At the end of the function if I do:
File.WriteAllBytes(#"D:\test.zip", ms.ToArray());
It creates a correctly structure archive sized 90mb but no files are extractable.
If I end with return ms.ToArray() it returns a ~130kb byte array.
Zip archive is broken because you read its content from MemoryStream before it is finished. In order to finish archive creation you need to call newArchive.Dispose() before calling ms.ToArray().
In this particular case you can do it like this:
using (var ms = new MemoryStream())
{
using (var newArchive = new ZipArchive(ms, ZipArchiveMode.Create, true))
{
foreach (var uncompressedEntry in entries)
{
var newEntry = newArchive.CreateEntry(uncompressedEntry.Key, newCompressionLevel);
using (var entryStream = newEntry.Open())
using (var writer = new BinaryWriter(entryStream, Encoding.UTF8))
{
writer.Write(uncompressedEntry.Value);
}
}
}
return ms.ToArray();
}

How to compress multiple files in zip file

I'm trying to compress two text files to a zip file. This is how my public method looks like:
public ActionResult Index()
{
byte[] file1 = System.IO.File.ReadAllBytes(#"C:\file1.txt");
byte[] file2 = System.IO.File.ReadAllBytes(#"C:\file2.txt");
Dictionary<string, byte[]> fileList = new Dictionary<string, byte[]>();
fileList.Add("file1.txt", file1);
fileList.Add("file2.txt", file2);
CompressToZip("zip.zip", fileList);
return View();
}
This is how my compress method looks like:
private void CompressToZip(string fileName, Dictionary<string, byte[]> fileList)
{
using (var memoryStream = new MemoryStream())
{
foreach (var file in fileList)
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
var demoFile = archive.CreateEntry(file.Key);
using (var entryStream = demoFile.Open())
using (var b = new BinaryWriter(entryStream))
{
b.Write(file.Value);
}
}
}
using (var fileStream = new FileStream(fileName, FileMode.Create))
{
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.CopyTo(fileStream);
}
}
}
In this approach, the zip folder is perfectly created. However the issue is I'm getting only one file inside the zip folder (Just the second file will be created inside the zip folder).
There are no errors found.
Question: How to compress both text files into the zip folder?
Thank you in advanced!
Your code is actually saving two separate zip archives to the zip.zip file (a new ZipArchive is created for each file to be compressed). The first zip archive contains only file1.txt, the second only file2.txt. When you open zip.zip in Windows Explorer, it shows just the contents of the second zip archive.
To create a single zip archive containing both files, just move the creation of the ZipArchive outside of your fileList loop:
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
foreach (var file in fileList)
{
var demoFile = archive.CreateEntry(file.Key);
using (var entryStream = demoFile.Open())
using (var b = new BinaryWriter(entryStream))
{
b.Write(file.Value);
}
}
}
At first glance, I would suggest that your foreach statement around the using var (archive = new ZipArchive...) is the wrong way round.
This way you are creating a new ZipArchive each time you iterate the foreach loop.
Surely you want to create the ZipArchive and loop through the foreach inside of that?
Like this:
private void CompressToZip(string fileName, Dictionary<string, byte[]> fileList)
{
using (var memoryStream = new MemoryStream())
{
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
foreach (var file in fileList)
{
var demoFile = archive.CreateEntry(file.Key);
using (var entryStream = demoFile.Open())
using (var b = new BinaryWriter(entryStream))
{
b.Write(file.Value);
}
}
}
using (var fileStream = new FileStream(fileName, FileMode.Create))
{
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.CopyTo(fileStream);
}
}
}
Hope this helps!
string startPath = #"c:\example\start";
string zipPath = #"c:\example\result.zip";
ZipFile.CreateFromDirectory(startPath, zipPath);

Categories