70

I am trying to create a Zip file in .NET 4.5 (System.IO.Compression) from a series of byte arrays. As an example, from an API I am using I end up with a List<Attachment> and each Attachment has a property called Body which is a byte[]. How can I iterate over that list and create a zip file that contains each attachment?

Right now I am under the impression that I would have to write each attachment to disk and create the zip file from that.

//This is great if I had the files on disk
ZipFile.CreateFromDirectory(startPath, zipPath);
//How can I create it from a series of byte arrays?
Justin Helgerson
  • 24,900
  • 17
  • 97
  • 124
  • Have you looked at the rest of the classes in [System.IO.Compression](http://msdn.microsoft.com/en-us/library/System.IO.Compression.aspx), particularly those that have _Stream_ in their name? – Austin Salonen Jun 20 '13 at 15:01
  • http://www.dotnetperls.com/gzipstream http://softdeveloping.blogspot.in/2012/01/example-of-compressing-and.html – Mohit Vashistha Jun 20 '13 at 15:04
  • @AustinSalonen - I may be missing something, but, I don't think I can have a GZipStream with multiple files as the output. – Justin Helgerson Jun 20 '13 at 16:00

3 Answers3

137

After a little more playing around and reading I was able to figure this out. Here is how you can create a zip file (archive) with multiple files without writing any temporary data to disk:

using (var compressedFileStream = new MemoryStream())
{
    //Create an archive and store the stream in memory.
    using (var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create, false)) {
        foreach (var caseAttachmentModel in caseAttachmentModels) {
            //Create a zip entry for each attachment
            var zipEntry = zipArchive.CreateEntry(caseAttachmentModel.Name);

            //Get the stream of the attachment
            using (var originalFileStream = new MemoryStream(caseAttachmentModel.Body))
            using (var zipEntryStream = zipEntry.Open()) {
                //Copy the attachment stream to the zip entry stream
                originalFileStream.CopyTo(zipEntryStream);
            }
        }
    }

    return new FileContentResult(compressedFileStream.ToArray(), "application/zip") { FileDownloadName = "Filename.zip" };
}
Justin
  • 6,373
  • 9
  • 46
  • 72
Justin Helgerson
  • 24,900
  • 17
  • 97
  • 124
  • is it cleaner if one uses `Close()` instead of nested `using`s? – sports Feb 16 '15 at 19:42
  • @sports - What are you calling `Close()` on? – Justin Helgerson Feb 16 '15 at 20:33
  • @JustinHelgerson Cannot access closed stream? Remove the outer using :-) – Luke T O'Brien Jun 17 '15 at 10:36
  • 15
    Friendly tip - If the ZipArchive class is unavailable, add a reference to the assembly: Right-Click `References` -> `Add Reference`. Under `Assemblies`, search for `System.IO` and check off `System.IO.Compression.FileSystem` and `System.IO.Compression`. – Levi Fuller Nov 29 '16 at 02:49
  • 4
    What is caseAttachmentModels? Also where is the byte[]? – Demodave Dec 22 '16 at 22:29
  • 1
    @sports I reformatted the code to use better layout of `using` statements. If you have multiple `using`s in a row without code in between, you do not have to nest them. – krillgar Oct 17 '17 at 12:27
  • what is populating compressedFileStream? – doganak Dec 01 '17 at 17:33
  • @doganak The `compressedFileStream` is passed into the constructor of the `ZipArchive` object and used internally. – Justin Helgerson Dec 01 '17 at 19:09
  • I know this is an old question, but I was wondering if there's actually compression happening here? Since this script only copies streams, I can't see if the resulting archive actually is compressed or not. Any idea? – Glubus Mar 07 '18 at 09:28
  • @Glubus It looks like there are 3 options: https://msdn.microsoft.com/en-us/library/system.io.compression.compressionlevel(v=vs.110).aspx. I believe the default is optimal. – Justin Helgerson Mar 10 '18 at 02:26
  • 3
    I used this but the "compressedFileStream" is always at 0 length... any ideas? – Zakaria Sahmane Jul 25 '18 at 09:54
  • 1
    @Demodave `caseAttachmentModels` are list of Attachments. `caseAttachmentModel.Body` is the byte[]. Read the question ;) – Abzoozy Dec 10 '18 at 10:46
  • @ZakariaSahmane if the "compressedFileStream" is 0 length, you need to put curly brackets after the first "using": using (var compressedFileStream = new MemoryStream()) { using () {} ... compressedFileStream.ToArray() ... } – belchev Mar 22 '19 at 13:52
  • 2
    compressedFileStream is always 0 bytes .please help me . – Sayed Azharuddin Apr 22 '19 at 05:57
  • @SayedAzharuddin I just edited the answer. `ZipArchiveMode.Update` should be `ZipArchiveMode.Create` or it won't work. I had the same issue as you. – Ben Baron Aug 21 '19 at 23:43
  • 1
    I used this instead, which is a bit simpler: https://stackoverflow.com/questions/48927574/create-zip-file-in-memory-from-bytes-text-with-arbitrary-encoding – John Gilmer Apr 26 '20 at 17:42
  • 5
    @krillgar your edit introduced a bug. The original has the return statement after the `zipArchive` is closed. By removing the nesting, you also now have the return statement inside the `zipArchive` `using` statement. By not disposing, the archives are not valid. – Zeph May 11 '20 at 19:25
  • 4
    @Zeph is correct. I had to add the braces around the original using statement and move the return statement one level out to make my zipArchive valid. – George Jun 11 '20 at 04:50
8

This is a variation of the great accepted answer posted by the OP. However, this is for WebForms instead of MVC. I'm working with the assumption that caseAttachmentModel.Body is a byte[]

Essentially everything is the same except with an additional method that sends the zip out as a Response.

using (var compressedFileStream = new MemoryStream()) {
    //Create an archive and store the stream in memory.
    using (var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Update, false))         {
     foreach (var caseAttachmentModel in caseAttachmentModels) {
        //Create a zip entry for each attachment
        var zipEntry = zipArchive.CreateEntry(caseAttachmentModel.Name);

        //Get the stream of the attachment
        using (var originalFileStream = new MemoryStream(caseAttachmentModel.Body)) {
                using (var zipEntryStream = zipEntry.Open()) {
                    //Copy the attachment stream to the zip entry stream
                    originalFileStream.CopyTo(zipEntryStream);
                }
            }
        }
    }
    sendOutZIP(compressedFileStream.ToArray(), "FileName.zip");
}

private void sendOutZIP(byte[] zippedFiles, string filename)
{
    Response.Clear();
    Response.ClearContent();
    Response.ClearHeaders();
    Response.ContentType = "application/x-compressed";
    Response.Charset = string.Empty;
    Response.Cache.SetCacheability(System.Web.HttpCacheability.Public);
    Response.AddHeader("Content-Disposition", "attachment; filename=" + filename);
    Response.BinaryWrite(zippedFiles);
    Response.OutputStream.Flush();
    Response.OutputStream.Close();
    Response.End();
}

I would also like to point out that advice given by @Levi Fuller on references in the accepted answer is spot on!

ShinyCy
  • 91
  • 1
  • 6
  • Can we set password to zip file? – Sachin Aug 15 '19 at 20:49
  • The documentation of the System.IO.Compression.ZipArchive (https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.ziparchive?view=netframework-4.8) doesn't show any options to password a zip file. According to this (oldish) thread (https://social.msdn.microsoft.com/Forums/sqlserver/en-US/c2303b11-cf85-41ea-b1a2-caf8b4042bda/systemiocompression-and-passwords?forum=netfxbcl) there is no password functionality in the .NET stuff and it suggests finding a third party library. – ShinyCy Aug 20 '19 at 17:36
2

GZipStream and DeflateStream seem like they would let you use steams/byte arrays to solve your problem, but maybe not with a compression file format usable by most users. (ie, your file would have a .gz extension) If this file is only used internally, that might be okay.

I don't know how you might make a ZIP using Microsoft's libraries, but I remember this library supporting the sort of things you might find useful: http://sevenzipsharp.codeplex.com/

It's licensed under LGPL.

Katana314
  • 8,429
  • 2
  • 28
  • 36
  • I need the end result to be multiple files which I don't believe GZipStream supports. – Justin Helgerson Jun 20 '13 at 16:09
  • OK, then try SevenZipSharp. It's built around the 7-zip library, but that can compress to a .zip file as well. There may also be similar libraries on Codeplex. – Katana314 Jun 20 '13 at 17:46