In this article, let’s look at some of the .NET classes we can use to read, create, and update zip files.

To download the source code for this article, you can visit our GitHub repository.

Today compressed (zip) files are all around us. Unless we have a really (really) old browser, we got this article in compact form to save bandwidth. After our browser asks, the server compresses the article and sends it to our browser. Then the browser uncompresses it and shows it to us.

Read Zip Files

We will more likely need to read/extract files from zip files than create zip files. So, first, let’s see how to read zip files, list their content, and extract files from them.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

In recent years, Microsoft has added support for zip files directly in .NET. For manipulating a single zip file, we use the ZipFile class. 

ZipFile Class

ZipFile is a static class, so we can’t create instances of it, but we can use its public methods. There are only four methods, and they are easy to understand:

MethodDescription
OpenOpens a zip file at a specified location using a specified mode and encoding.
OpenReadOpens a zip file for reading.
ExtractToDirectoryExtracts all files in the specified file to a directory on the file system.
CreateFromDirectoryCreates a zip file that contains the files and directories from the specified directory.

Each of them can have different parameters. So we can specify the encoding, compression level, and other parameters. 

List All Files Contained in the Zip File

Let’s have a look at how to list the files in a zip file:

using var zipFile = ZipFile.OpenRead("multi-folder.zip");

var counter = 0;
foreach (var entry in zipFile.Entries)
    Console.WriteLine($"{++counter,3}: {entry.Name}");

To list all the files in a zip file, we have to open the file and loop through the files. We can open the file with the OpenRead() method, as we only read it. After that, we ask the object for the list of all files and display them:

 1: image.png
 2: text-file.txt
 3:
 4: image.png
 5:
 6: image.png
 7:
 8: image.png
 9:
10: image.png

This is strange output, as one file, named image.png, is displayed multiple times. But not only that, there are even empty files. Why? This zip file has a folder structure inside, and these empty spaces are folders. Also, image.png files are in different folders.

Let’s use the FullName property instead of the Name property to display the actual structure of the zip file and folder names. So, all we need to change is the WriteLine() method:

using var zipFile = ZipFile.OpenRead("multi-folder.zip");

var counter = 0;
foreach (var entry in zipFile.Entries)
    Console.WriteLine($"{++counter,3}: {entry.FullName}");

By running this code, we get the full structure of the zip file:

 1: image.png
 2: text-file.txt
 3: folder1/
 4: folder1/image.png
 5: folder1/subfolder11/
 6: folder1/subfolder11/image.png
 7: folder1/subfolder11/subfolder111/
 8: folder1/subfolder11/subfolder111/image.png
 9: folder2/
10: folder2/image.png

ZipArchiveEntry Class

The ZipArchiveEntry class represents a single file (an entry) inside the zip file. We have already used two properties, namely Name, and FileName. This class enables us to examine the properties of an entry, and open or delete the entry. As with the ZipFile class, we only discuss the most used properties and methods of the ZipArchiveEntry class.

Of all properties, besides Name and FullName, we should mention Length which returns the uncompressed size of the entry.

There are two valuable methods and one very useful extension method. The Delete() method deletes the entry, the Open() method opens the entry and the ExtractToFile() extension method extracts an entry to a file.

The Open() method opens an entry Stream and we can operate on that entry as on any other stream. But most of the time we rather use the ExtractToFile() extension method as it does what we most likely need. It extracts the entry from the zip file onto the file system, so we don’t have to work with streams.

Extract Files From Zip File With ExtractToDirectory() Method

If we are resourceful, we can extract all files (all entries) by using one function:

ZipFile.ExtractToDirectory("multi-folder.zip", "extracted-files");

This single line of code extracts all the files in the multi-folder.zip file into the subfolder extracted-files in our current folder. 

But there is a catch. If we run our program again, we get an exception:

System.IO.IOException
  HResult=0x80070050
  Message=The file '...\image.png' already exists.
  Source=System.Private.CoreLib
  StackTrace: ...

This method prevents us from accidentally overriding existing files. But if we do want to override files and we are not afraid of losing something important, we can use the third parameter overwriteFiles:

ZipFile.ExtractToDirectory("multi-folder.zip", "extracted-files", true);

Note, this code overwrites any existing files!

Now that the files are on the disk, we can manipulate them as any other.

Extract Files From Zip File With ExtractToFile() Method

So we can extract the whole folder with one method, but as we saw, the class ZipArchiveEntry also has the ExtractToFile() extension method. It extracts concrete entries to a selected folder.

When we use ExtractToFile() method, we always have to define a whole path; otherwise, we will save the file in the current folder, which is rarely a good idea. So let us write our version of theExtractToDirectory() method, where we will have to think about the correct folders and sub-folders:

using var zipFile = ZipFile.OpenRead("multi-folder.zip");
var rootFolder = "extracted-files";
foreach (var entry in zipFile.Entries)
{
    var wholePath = Path.Combine(
        rootFolder,
        Path.GetDirectoryName(entry.FullName) ?? string.Empty);

    if (!Directory.Exists(wholePath))
        Directory.CreateDirectory(wholePath);

    if (!string.IsNullOrEmpty(entry.Name))
    {
        var wholeFileName = Path.Combine(
            wholePath,
            Path.GetFileName(entry.FullName));

        entry.ExtractToFile(wholeFileName, true);
    }
}

First, we have to open the file and set the root folder, and then loop through entries. In the loop, we extract the folder path from the entry. We do that by using the Path.GetDirectoryName() method. After extracting the folder, we combine it with the root folder. This is done with the Path.Combine() method. If the folder does not exist, we create it with the Directory.CreateDirectory() method.

Now we come to a well-known problem. How do we know if an entry is a file or a folder? Strangely, Microsoft did not create a method for that. There are a few possibilities, but the easiest is this: if the Name is empty, then this is a folder. Otherwise, it is a file.

So if the Name property is not empty, then this entry is a file, and we have to again combine the whole path with the file name to get the correct file on the disk. After we get the file name, we can use the ExtractToFile() method and extract the entry to the disk. Again we have to be careful with the second parameter. If set to true, we override the file if it already exists.

So, it was quite a journey, but we have learned a lot and written a nice piece of code. 

Extract Files With Open() Method

We can access the Stream using the Open() method if we don’t need the file on disk but only its content. Let’s convert each file to its base64 string, so we can send them by email (or something similar):

using var zipFile = ZipFile.OpenRead("multi-folder.zip");
foreach (var entry in zipFile.Entries)
{
    if (!string.IsNullOrEmpty(entry.Name))
    {
        using (var stream = entry.Open())
        using (var memoryStream = new MemoryStream())
        {
            stream.CopyTo(memoryStream);
            var bytes = memoryStream.ToArray();
            var base64 = Convert.ToBase64String(bytes);
            Console.WriteLine($"{entry.FullName} => {base64}");
        }
    }
}

Here, we are converting all files to their base64 string representation. We loop through the Entries and convert only files. We execute the conversion by converting the Stream into a Base64 string. 

Create Zip Files

Creating zip files is just as easy as reading them. Again we can do that on different levels. So we can create a zip file from the whole folder and its subfolders using one single line.

Compress (Zipping) Whole Folder

The easiest method to create a zip file is to compress the whole folder:

ZipFile.CreateFromDirectory(".", @"..\parent.zip");

With this one line of code, we are compressing the whole current folder into a file named parent.zip. Also, we can compress any other folder on disk:

ZipFile.CreateFromDirectory(@"c:\temp", "my-temp.zip");

This compresses the whole temp folder into the file my-temp.zip.

Add Files to a Zip File

As with extracting entries from the zip file, we can also add entries to zip files:

var folder = new DirectoryInfo(".");
FileInfo[] files = folder.GetFiles("*.*", SearchOption.AllDirectories);

using var archive = ZipFile.Open(@"..\parent.zip", ZipArchiveMode.Create);

foreach (var file in files)
{
    archive.CreateEntryFromFile(
        file.FullName,
        Path.GetRelativePath(folder.FullName, file.FullName)
    );
}

To do the same work as the CreateFromDirectory() method, we have to loop through files. First, we use DirectoryInfo to get the list of all files in a folder. Next, we create a new zip file and, in a foreach loop, add each file to the newly created zip file with the CreateEntryFromFile() method.

Again we have 12 lines of code instead of 1. But this is just an example, as with that code we have more freedom. We can compress only selected files and not all files:

var folder = new DirectoryInfo(".");
FileInfo[] files = folder.GetFiles("*.txt", SearchOption.AllDirectories);

using var archive = ZipFile.Open(@"..\parent.zip", ZipArchiveMode.Create);

foreach (var file in files)
{
    archive.CreateEntryFromFile(
        file.FullName,
        Path.GetRelativePath(folder.FullName, file.FullName)
    );
}

To compress only *.txt files, we have to change only one line of code.

Create a Zip File With Stream

We have created a zip with a single line of code. Then we also created a zip file by manually adding files. The third option is using Streams:

var helloText = "Hello world!";

using var archive = ZipFile.Open(@"..\test.zip", ZipArchiveMode.Create);

var entry = archive.CreateEntry("hello.txt");

using (Stream st = entry.Open())
using (StreamWriter writerManifest = new StreamWriter(st))
writerManifest.WriteLine(helloText);

Here, we create a new zip file named test.zip containing a single file named hello.txt with the text “Hello world”.

Compression Level

Sometimes when creating zip files, it is also important how much we want to compress them. For that reason methods also accept a compression-level parameter. If we do not specify the compression level, then .NET uses the default.

We have four options, ranging from no compression at all to the best compression: NoCompression, Fastest, Optimal, and SmallestSize. With compression, there is a trade-off with speed. So the more compression we want, the slower the program will get, but the result will be a smaller file. 

In our previous example, we could demand the best possible compression by setting the parameter to CompressionLevel.SmallestSize:

var entry = archive.CreateEntry("hello.txt", CompressionLevel.SmallestSize);

This code uses the best but slowest possible compression.

Note: with our small zip file containing one TXT file with the text ‘Hello world’ compression is not an issue. But with larger zip files, there can be a significant difference in size.

Delete Entries From Zip Files

We know how to read and write zip files, so there is only one rarely-used method left, Delete(). We can in fact, also delete entries from zip files.

So let’s delete all files with the name “image.png” from a zip file:

using var zipFile = ZipFile.Open("multi-folder.zip", ZipArchiveMode.Update);

var images = zipFile.Entries.Where(e => e.Name == "image.png").ToList();

for (int i = images.Count - 1; i >= 0; --i)
    images[i].Delete();

First, we open the zip file for modifications. Then we select all files named “image.png” from the Entries property. After that, we delete them by using the Delete() method.

Conclusion

Working with zip files in .NET is a joy. Objects are easy to use, and the code is clean.

When working with zip files, we mostly want to compress the whole folder on a disk or uncompress the whole zip file onto a disk. Fortunately, we can achieve both operations with a single line of code.

We looked at how to add files (an entry) to a zip file, as well as extract them. If we want to dig even deeper, we can always use Streams.

But, as always, there is one piece missing. If we want to use password-protected zip files, we should use some external libraries.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!