How do I ZIP a file in C#, using no 3rd-party APIs?
Asked Answered
M

8

202

I'm pretty sure this is not a duplicate so bear with me for just a minute.

How can I programatically (C#) ZIP a file (in Windows) without using any third party libraries? I need a native windows call or something like that; I really dislike the idea of starting a process, but I will if I absolutely have to. A PInovke call would be much better.

Failing that, let me tell you what I'm really trying to accomplish: I need the ability to let a user download a collection of documents in a single request. Any ideas on how to accomplish this?

Maximilien answered 2/6, 2009 at 16:41 Comment(6)
Compress Zip files with Windows Shell API and C#Braden
@Chesso: Yes, from an ASPX page.Maximilien
I found this example usefull when I was searching for the same thing a few weeks ago: syntaxwarriors.com/2012/…Pelasgian
If using the 4.5 Framework, there is now the ZipArchive and ZipFile classes.Singapore
Anyone used DotNetZip??Rhizoid
A Pure C# Class to Store Files in Zip (MIT licensed)Dilapidated
P
88

Are you using .NET 3.5? You could use the ZipPackage class and related classes. Its more than just zipping up a file list because it wants a MIME type for each file you add. It might do what you want.

I'm currently using these classes for a similar problem to archive several related files into a single file for download. We use a file extension to associate the download file with our desktop app. One small problem we ran into was that its not possible to just use a third-party tool like 7-zip to create the zip files because the client side code can't open it -- ZipPackage adds a hidden file describing the content type of each component file and cannot open a zip file if that content type file is missing.

Photosphere answered 2/6, 2009 at 16:46 Comment(4)
Note that this doesn't always work in reverse. Some Zip files will not rehydrate using the ZipPackage class. Files made with ZipPackage will so you should be good.Trousseau
Note that ZipPackage cannot append to an existing zipped package.Northrop
Sigh: "The type or namespace "Packaging" does not exist in namespace "System.IO".Rhizoid
(Answer to the above "sigh": Open "References" and add (illogically enough) "WindowsBase".)Rhizoid
S
370

How can I programatically (C#) ZIP a file (in Windows) without using any third party libraries?

If using the 4.5+ Framework, there is now the ZipArchive and ZipFile classes.

using (ZipArchive zip = ZipFile.Open("test.zip", ZipArchiveMode.Create))
{
    zip.CreateEntryFromFile(@"c:\something.txt", "data/path/something.txt");
}

You need to add references to:

  • System.IO.Compression
  • System.IO.Compression.FileSystem

For .NET Core targeting net46, you need to add dependencies for

  • System.IO.Compression
  • System.IO.Compression.ZipFile

Example project.json:

"dependencies": {
  "System.IO.Compression": "4.1.0",
  "System.IO.Compression.ZipFile": "4.0.1"
},

"frameworks": {
  "net46": {}
}

For .NET Core 2.0, just adding a simple using statement is all that is needed:

  • using System.IO.Compression;
Singapore answered 25/11, 2013 at 18:0 Comment(10)
How has this not gotten more upvotes? It's the only direct answer.Hargrave
Because the question is five years old, whereas this answer is only two months old. Derp :-PWhet
@heliac still the Stackoverflow thingie should be a question and answers repository and in the spirit the best answer shoudl be on top... (damn, i knew this does not work)Philippi
Just in case it helps anybody, the second argument is the file entry. This is the path to which the file will be extracted relative to the unzip folder. In Windows 7, I found that if the file entry is a full path, e.g., @"D:\Temp\file1.pdf", the native Windows extractor fails. You may run into this issue if you simply use the filenames resulting from Directory.GetFiles(). Best to extract the file name using Path.GetFileName() for the file entry argument.Hoskinson
I don't seem to be able to find this in 4.5.2?Crabwise
@user3791372, you need to explicitly add the reference to system.io.compression. simply having system won't work.Roughish
Also remember ZipFile.CreateFromDirectory(filesDirectory, zipFileName) can be used to zip a whole directoryWilled
Looks like it doesn't support passwords if that's a requirementMaxwell
I found I need zip.Dispose() after the zip.CreateEntryFromFile, else the created file stays open. I previously beleived "using" did the dispose by itself. And, yes, I'm deleting the zip file outside the using clause.Desmarais
I guess MS again made too limited library - I cannot specify "PPMd" method of compression (best for text files).Ginn
P
88

Are you using .NET 3.5? You could use the ZipPackage class and related classes. Its more than just zipping up a file list because it wants a MIME type for each file you add. It might do what you want.

I'm currently using these classes for a similar problem to archive several related files into a single file for download. We use a file extension to associate the download file with our desktop app. One small problem we ran into was that its not possible to just use a third-party tool like 7-zip to create the zip files because the client side code can't open it -- ZipPackage adds a hidden file describing the content type of each component file and cannot open a zip file if that content type file is missing.

Photosphere answered 2/6, 2009 at 16:46 Comment(4)
Note that this doesn't always work in reverse. Some Zip files will not rehydrate using the ZipPackage class. Files made with ZipPackage will so you should be good.Trousseau
Note that ZipPackage cannot append to an existing zipped package.Northrop
Sigh: "The type or namespace "Packaging" does not exist in namespace "System.IO".Rhizoid
(Answer to the above "sigh": Open "References" and add (illogically enough) "WindowsBase".)Rhizoid
Z
16
    private static string CompressFile(string sourceFileName)
    {
        using (ZipArchive archive = ZipFile.Open(Path.ChangeExtension(sourceFileName, ".zip"), ZipArchiveMode.Create))
        {
            archive.CreateEntryFromFile(sourceFileName, Path.GetFileName(sourceFileName));
        }
        return Path.ChangeExtension(sourceFileName, ".zip");
    }
Zoometry answered 21/4, 2015 at 1:59 Comment(2)
How can I get sourceFileName when I am inside a webapi, receiving a HttpContext.Current.Request ?Spacious
Compress more one file ?Aerobic
L
12

I was in the same situation, wanting to .NET instead of a third party library. As another poster mentioned above, simply using the ZipPackage class (introduced in .NET 3.5) is not quite enough. There is an additional file that MUST be included in the archive in order for the ZipPackage to work. If this file is added, then the resulting ZIP package can be opened directly from Windows Explorer - no problem.

All you have to do is add the [Content_Types].xml file to the root of the archive with a "Default" node for every file extension you wish to include. Once added, I could browse the package from Windows Explorer or programmatically decompress and read its contents.

More information on the [Content_Types].xml file can be found here: http://msdn.microsoft.com/en-us/magazine/cc163372.aspx

Here is a sample of the [Content_Types].xml (must be named exactly) file:

<?xml version="1.0" encoding="utf-8" ?>
<Types xmlns=
    "http://schemas.openxmlformats.org/package/2006/content-types">
  <Default Extension="xml" ContentType="text/xml" /> 
  <Default Extension="htm" ContentType="text/html" /> 
  <Default Extension="html" ContentType="text/html" /> 
  <Default Extension="rels" ContentType=
    "application/vnd.openxmlformats-package.relationships+xml" /> 
  <Default Extension="jpg" ContentType="image/jpeg" /> 
  <Default Extension="png" ContentType="image/png" /> 
  <Default Extension="css" ContentType="text/css" /> 
</Types>

And the C# for creating a ZIP file:

var zipFilePath = "c:\\myfile.zip"; 
var tempFolderPath = "c:\\unzipped"; 

    using (Package package = ZipPackage.Open(zipFilePath, FileMode.Open, FileAccess.Read)) 
    { 
        foreach (PackagePart part in package.GetParts()) 
        { 
            var target = Path.GetFullPath(Path.Combine(tempFolderPath, part.Uri.OriginalString.TrimStart('/'))); 
            var targetDir = target.Remove(target.LastIndexOf('\\')); 

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

            using (Stream source = part.GetStream(FileMode.Open, FileAccess.Read)) 
            { 
                source.CopyTo(File.OpenWrite(target)); 
            } 
        } 
    } 

Note:

Lientery answered 1/6, 2012 at 12:29 Comment(1)
Nice sample, but it doesn't create a ZIP file. It unzips an existing file.Jacal
S
1

Based off Simon McKenzie's answer to this question, I'd suggest using a pair of methods like this:

    public static void ZipFolder(string sourceFolder, string zipFile)
    {
        if (!System.IO.Directory.Exists(sourceFolder))
            throw new ArgumentException("sourceDirectory");

        byte[] zipHeader = new byte[] { 80, 75, 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

        using (System.IO.FileStream fs = System.IO.File.Create(zipFile))
        {
            fs.Write(zipHeader, 0, zipHeader.Length);
        }

        dynamic shellApplication = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
        dynamic source = shellApplication.NameSpace(sourceFolder);
        dynamic destination = shellApplication.NameSpace(zipFile);

        destination.CopyHere(source.Items(), 20);
    }

    public static void UnzipFile(string zipFile, string targetFolder)
    {
        if (!System.IO.Directory.Exists(targetFolder))
            System.IO.Directory.CreateDirectory(targetFolder);

        dynamic shellApplication = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
        dynamic compressedFolderContents = shellApplication.NameSpace(zipFile).Items;
        dynamic destinationFolder = shellApplication.NameSpace(targetFolder);

        destinationFolder.CopyHere(compressedFolderContents);
    }
}
Science answered 8/10, 2014 at 11:42 Comment(0)
C
1

There is now an officially documented section in the dotnet docs on how to compress and decompress without third-party libraries. It includes full code samples too!

https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-compress-and-extract-files

Chignon answered 17/6, 2022 at 21:19 Comment(0)
S
0

Add these 4 functions to your project:

        public const long BUFFER_SIZE = 4096;
    public static void AddFileToZip(string zipFilename, string fileToAdd)
    {
        using (Package zip = global::System.IO.Packaging.Package.Open(zipFilename, FileMode.OpenOrCreate))
        {
            string destFilename = ".\\" + Path.GetFileName(fileToAdd);
            Uri uri = PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative));
            if (zip.PartExists(uri))
            {
                zip.DeletePart(uri);
            }
            PackagePart part = zip.CreatePart(uri, "", CompressionOption.Normal);
            using (FileStream fileStream = new FileStream(fileToAdd, FileMode.Open, FileAccess.Read))
            {
                using (Stream dest = part.GetStream())
                {
                    CopyStream(fileStream, dest);
                }
            }
        }
    }
    public static void CopyStream(global::System.IO.FileStream inputStream, global::System.IO.Stream outputStream)
    {
        long bufferSize = inputStream.Length < BUFFER_SIZE ? inputStream.Length : BUFFER_SIZE;
        byte[] buffer = new byte[bufferSize];
        int bytesRead = 0;
        long bytesWritten = 0;
        while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) != 0)
        {
            outputStream.Write(buffer, 0, bytesRead);
            bytesWritten += bytesRead;
        }
    }
    public static void RemoveFileFromZip(string zipFilename, string fileToRemove)
    {
        using (Package zip = global::System.IO.Packaging.Package.Open(zipFilename, FileMode.OpenOrCreate))
        {
            string destFilename = ".\\" + fileToRemove;
            Uri uri = PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative));
            if (zip.PartExists(uri))
            {
                zip.DeletePart(uri);
            }
        }
    }
    public static void Remove_Content_Types_FromZip(string zipFileName)
    {
        string contents;
        using (ZipFile zipFile = new ZipFile(File.Open(zipFileName, FileMode.Open)))
        {
            /*
            ZipEntry startPartEntry = zipFile.GetEntry("[Content_Types].xml");
            using (StreamReader reader = new StreamReader(zipFile.GetInputStream(startPartEntry)))
            {
                contents = reader.ReadToEnd();
            }
            XElement contentTypes = XElement.Parse(contents);
            XNamespace xs = contentTypes.GetDefaultNamespace();
            XElement newDefExt = new XElement(xs + "Default", new XAttribute("Extension", "sab"), new XAttribute("ContentType", @"application/binary; modeler=Acis; version=18.0.2application/binary; modeler=Acis; version=18.0.2"));
            contentTypes.Add(newDefExt);
            contentTypes.Save("[Content_Types].xml");
            zipFile.BeginUpdate();
            zipFile.Add("[Content_Types].xml");
            zipFile.CommitUpdate();
            File.Delete("[Content_Types].xml");
            */
            zipFile.BeginUpdate();
            try
            {
                zipFile.Delete("[Content_Types].xml");
                zipFile.CommitUpdate();
            }
            catch{}
        }
    }

And use them like this:

foreach (string f in UnitZipList)
{
    AddFileToZip(zipFile, f);
    System.IO.File.Delete(f);
}
Remove_Content_Types_FromZip(zipFile);
Stitching answered 22/1, 2019 at 8:37 Comment(0)
P
-1

Looks like Windows might just let you do this...

Unfortunately I don't think you're going to get around starting a separate process unless you go to a third party component.

Persona answered 2/6, 2009 at 16:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.