2

I tried following this SO: Create zip file from byte[] as a dummy project and it looks like this:

using System.IO.Compression;
using System.IO;
using System.Net.Http;
using System;

namespace TestApp
{
    internal class Program
    {
        static void Main(string[] args)
        {
            using var compressedFileStream = new MemoryStream();
            using var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create);

            //Create a zip entry for each attachment
            var zipEntry = zipArchive.CreateEntry("test.txt");
            var file = File.ReadAllBytes("test.txt");

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

            var toarraybaby = compressedFileStream.ToArray();

            File.WriteAllBytes("hehe.zip", toarraybaby);
        }
    }
}

I get a .zip file as output and the file has a size. But when trying to open the file I get that its corrupt. What am I missing?

President Camacho
  • 1,860
  • 6
  • 27
  • 44
  • 1
    There's no `MemoryStream` constructor that has a single `string` parameter so that code wouldn't compile. – John Aug 18 '22 at 07:13
  • Why use a `MemoryStream` and then save its contents to a file rather than just using a `FileStream` in the first place? The original code never creates a file so a `MemoryStream` makes sense but it doesn't in your case. – John Aug 18 '22 at 07:14
  • I changed it to new `FileStream("test.txt", FileMode.Open);` is that what you mean? I'm still getting the corrupt file. – President Camacho Aug 18 '22 at 07:20
  • And in my original code I'm not reading a File or saving a File either. I'm doing exactly what the SO I'm referencing to exactly. But sending it as a http request. – President Camacho Aug 18 '22 at 07:21

3 Answers3

3

What's wrong with that code specifically is that a C# 8-style using declaration disposes the at end of the current scope. That's too late. The zip archive has to be disposed to ensure it writes all the necessary data to its output stream, but by the time it does that, Main has ended.

There are various ways to ensure it gets disposed earlier, for example:

using var compressedFileStream = new MemoryStream();
// using an old-style using statement here to ensure the zip archive gets disposed early enough
using (var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create))
{
    //Create a zip entry for each attachment
    var zipEntry = zipArchive.CreateEntry("test.txt");
    var file = File.ReadAllBytes("test.txt");

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

}
var toarraybaby = compressedFileStream.ToArray();

File.WriteAllBytes("hehe.zip", toarraybaby);
harold
  • 61,398
  • 6
  • 86
  • 164
  • Or you can flush it – Charlieface Aug 18 '22 at 10:37
  • @Charlieface ZipArchive in .net core does not have Flush(), and flushing the streams did not help. It looks like the only way to force ZipArchive to write everything out is to dispose it before accessing the compressed bytes. – JustAMartin Dec 22 '22 at 17:15
  • Seems you are right, judging by [the source code](https://github.com/dotnet/runtime/blob/main/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.cs). But as I note in my own answer below, there was no need for the intervening `MemoryStream` anyway – Charlieface Dec 23 '22 at 12:56
2

The problem is that you are not flushing the Zip into the MemoryStream until it gets closed at the end of the function. You could manually put in a Flush or close the using at the right place.

But ideally you would not use a MemoryStream at all. Instead, just feed the zip straight into a FileStream. Also you should use async if possible.

static async Task Main(string[] args)
{
    using var compressedFileStream = new FileStream("hehe.zip", FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, true);
    using var zipArchive = new ZipArchive(compressedFileStream, ZipArchiveMode.Create);

    //Create a zip entry for each attachment
    var zipEntry = zipArchive.CreateEntry("test.txt");
    using var originalFileStream = new FileStream("test.txt", FileMode.Open, FileAccess.ReadWrite, FileShare.None, 4096, true);

    //Get the stream of the attachment
    using var zipEntryStream = zipEntry.Open();

    //Copy the attachment stream to the zip entry stream
    await originalFileStream.CopyToAsync(zipEntryStream);
}
Charlieface
  • 52,284
  • 6
  • 19
  • 43
1

I just used the following code and it worked for me:

var folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var outputFilePath = Path.Combine(folderPath, "Test.zip");

using var outputFile = File.Create(outputFilePath);
using var archive = new ZipArchive(outputFile, ZipArchiveMode.Create);

var inputFileName = "Test.txt";
var inputFilePath = Path.Combine(folderPath, inputFileName);
var entry = archive.CreateEntry(inputFileName);

using var inputFile = File.OpenRead(inputFilePath);
using var entryStream = entry.Open();

inputFile.CopyTo(entryStream);

I was able to open that ZIP file in File Explorer and then open the text file it contained and see the same original text. If you want to create a new ZIP file containing arbitrary existing files, that's how I'd do it.

I then tried replacing the output FileStream with a MemoryStream while making the fewest changes to the code as possible and got this:

var folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var outputFilePath = Path.Combine(folderPath, "Test.zip");

using var outputStream = new MemoryStream();
using var archive = new ZipArchive(outputStream, ZipArchiveMode.Create);

var inputFileName = "Test.txt";
var inputFilePath = Path.Combine(folderPath, inputFileName);
var entry = archive.CreateEntry(inputFileName);

using var inputFile = File.OpenRead(inputFilePath);
using var entryStream = entry.Open();

inputFile.CopyTo(entryStream);

var data = outputStream.ToArray();

File.WriteAllBytes(outputFilePath, data);

When I ran that, I ended up with a corrupt ZIP file. I'm not sure what the specific reason is but, if my first code snippet works for you, it's not really worth finding out. If you really need to use a MemoryStream, let me know and I'll investigate a bit further. It may have to do with the order of disposing objects but I'm not sure.

John
  • 3,057
  • 1
  • 4
  • 10
  • It works, thanks! Its not important with memorystream just that the output is a working zip – President Camacho Aug 18 '22 at 08:05
  • Or well I actually need to use the memorystream because in my live project I don't got physical files. I got byte array files. – President Camacho Aug 18 '22 at 08:13
  • @PresidentCamacho, I will do a bit more testing and update my answer with a working example that outputs a valid archive using a `MemoryStream`. I'll comment again when that's done, so you receive a notification. – John Aug 18 '22 at 08:53