Peter S
Peter S

Reputation: 53

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!

Upvotes: 5

Views: 2827

Answers (2)

vik_78
vik_78

Reputation: 1157

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.

Upvotes: 2

Jon
Jon

Reputation: 3255

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

Upvotes: 3

Related Questions