ZipArchive создает неверный ZIP-файл

Я пытаюсь создать новый ZIP-пакет из кода с одной записью и сохранить ZIP-файл в файл. Я пытаюсь достичь этого с помощью класса System.IO.Compression.ZipArchive. Я создаю ZIP-пакет со следующим кодом:

using (MemoryStream zipStream = new MemoryStream())
{
    using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create))
    {
        var entry = zip.CreateEntry("test.txt");
        using (StreamWriter sw = new StreamWriter(entry.Open()))
        {
            sw.WriteLine(
                "Etiam eros nunc, hendrerit nec malesuada vitae, pretium at ligula.");
        }

Затем я сохраняю ZIP в файл либо в WinRT:

        var file = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync("test.zip", CreationCollisionOption.ReplaceExisting);
        zipStream.Position = 0;
        using (Stream s = await file.OpenStreamForWriteAsync())
        {
            zipStream.CopyTo(s);
        }

Или в нормальном.NET 4.5:

        using (FileStream fs = new FileStream(@"C:\Temp\test.zip", FileMode.Create))
        {
            zipStream.Position = 0;
            zipStream.CopyTo(fs);
        }

Однако я не могу открыть созданные файлы ни в проводнике Windows, ни в WinRAR и т. Д. (Я проверил, что размер созданного файла соответствует длине zipStream, поэтому сам поток был правильно сохранен в файл.)
Я делаю что-то не так или есть проблема с классом ZipArchive?

7 ответов

Решение

Я обнаружил - в ретроспективной, очевидной - ошибку в моем коде. ZipArchive должен быть удален, чтобы заставить его записывать содержимое в основной поток. Поэтому мне пришлось сохранить поток в файл после окончания использования блока ZipArchive.
И было важно установить аргумент leftOpen его конструктора в true, чтобы он не закрывал основной поток. Итак, вот полное рабочее решение:

using (MemoryStream zipStream = new MemoryStream())
{
    using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
    {
        var entry = zip.CreateEntry("test.txt");
        using (StreamWriter sw = new StreamWriter(entry.Open()))
        {
            sw.WriteLine(
                "Etiam eros nunc, hendrerit nec malesuada vitae, pretium at ligula.");
        }
    }

    var file = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync(
        "test.zip",
        CreationCollisionOption.ReplaceExisting);

    zipStream.Position = 0;
    using (Stream s = await file.OpenStreamForWriteAsync())
    {
        zipStream.CopyTo(s);
    }
}
// Create file "archive.zip" in current directory use it as destination for ZIP archive
using (var zipArchive = new ZipArchive(File.OpenWrite("archive.zip"), 
ZipArchiveMode.Create))
{
    // Create entry inside ZIP archive with name "test.txt"
    using (var entry = zipArchive.CreateEntry("test.txt").Open())
    {
        // Copy content from current directory file "test.txt" into created ZIP entry 
        using (var file = File.OpenRead("test.txt"))
        {
            file.CopyTo(entry);
        }
    }
}    

В результате вы получите архив "archive.zip" с одним файлом ввода "test.txt". Вам нужен этот каскад using избегать какого-либо взаимодействия с уже размещенными ресурсами.

На всех ваших объектах Stream вы должны перематывать потоки с самого начала, чтобы другие приложения могли правильно их прочитать, используя метод.Seek.

Пример:

zipStream.Seek(0, SeekOrigin.Begin);

В моем случае проблема заключалась в том, что я не удалял "zipArchive", когда задача zip-файла завершилась. Хотя я промывал и закрывал все ручьи.

Итак, либо используйте использование, как предложено в ответах выше

using (var zipArchive = new ZipArchive(File.OpenWrite("archive.zip"), 
ZipArchiveMode.Create))

Или удалите переменную в конце задачи...

zipArchive.Dispose();

Вы можете следовать той же идее, только в обратном порядке, используя файловый поток как источник. Я сделал форму ниже, и файл открылся нормально:

string fileFormat = ".zip"; // any format
string filename = "teste" + fileformat;

StorageFile zipFile = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync(filename,CreationCollisionOption.ReplaceExisting);

using (Stream zipStream = await zipFile.OpenStreamForWriteAsync()){

   using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create)){
        //Include your content here
   }
}

Будьте осторожны, если используются инструкции без фигурных скобок (C# > 8.0):

      static byte[] CompressAsZip(string fileName, Stream fileStram)
{
    using var memoryStream = new MemoryStream();
    using var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, false);
    using var entryStream = archive.CreateEntry(fileName).Open();

    fileStram.CopyTo(entryStream);

    archive.Dispose(); //<- Necessary to close the archive correctly
    return memoryStream.ToArray();
}

Используя оператор using с фигурными скобками, archive.Dispose будет неявно вызываться в конце области видимости. Кроме того, поскольку вы используете C# >8.0, я рекомендую асинхронную реализацию этого типа, способную архивировать несколько файлов и отменять ее:

      static async Task<byte[]> CompressAsZipAsync(
    IAsyncEnumerable<(string FileName, Stream FileStream)> files,
    bool disposeStreamsAfterCompression = false,
    CancellationToken cancellationToken = default)
{
    using var memoryStream = new MemoryStream();
    using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, false))
    {
        await foreach (var (FileName, FileStream) in files.WithCancellation(cancellationToken))
        {
            using var entryStream = archive.CreateEntry(FileName).Open();
            await FileStream.CopyToAsync(entryStream, cancellationToken);

            if (disposeStreamsAfterCompression)
                await FileStream.DisposeAsync();
        }
    }
    return memoryStream.ToArray();
}

Полный код выглядит так:

var file = await Windows.Storage.ApplicationData.Current.LocalFolder.CreateFileAsync("test.zip",CreationCollisionOption.ReplaceExisting);
using (Stream zipStream = await zipFile.OpenStreamForWriteAsync())
{
    using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
    {
        var entry = zip.CreateEntry("test.txt");
        using (StreamWriter sw = new StreamWriter(entry.Open()))
        {
            sw.WriteLine("Etiam eros nunc, hendrerit nec malesuada vitae, pretium at ligula.");
        }
    }   
}
Другие вопросы по тегам