Неверный zip-файл после его создания с помощью System.IO.Compression

Я пытаюсь создать ZIP-файл, который содержит один или несколько файлов.
Я использую.NET Framework 4.5 и более конкретно пространство имен System.IO.Compression.
Цель состоит в том, чтобы позволить пользователю загружать ZIP-файл через приложение ASP.NET MVC.
Zip-файл создается и отправляется клиенту, но когда я пытаюсь открыть его, дважды щелкнув по нему, я получаю следующую ошибку:
Windows не может открыть папку. Сжатая (заархивированная) папка... недействительна.
Вот мой код:

[HttpGet]
public FileResult Download()
{
    var fileOne = CreateFile(VegieType.POTATO);
    var fileTwo = CreateFile(VegieType.ONION);
    var fileThree = CreateFile(VegieType.CARROT);

    IEnumerable<FileContentResult> files = new List<FileContentResult>() { fileOne, fileTwo, fileThree };
    var zip = CreateZip(files);

    return zip;
}

private FileContentResult CreateFile(VegieType vType)
{
    string fileName = string.Empty;
    string fileContent = string.Empty;

    switch (vType)
    {
        case VegieType.BATATA:
            fileName = "batata.csv";
            fileContent = "THIS,IS,A,POTATO";
            break;
        case VegieType.CEBOLA:
            fileName = "cebola.csv";
            fileContent = "THIS,IS,AN,ONION";
            break;
        case VegieType.CENOURA:
            fileName = "cenoura.csv";
            fileContent = "THIS,IS,A,CARROT";
            break;
        default:
            break;
    }

    var fileBytes = Encoding.GetEncoding(1252).GetBytes(fileContent);
    return File(fileBytes, MediaTypeNames.Application.Octet, fileName);
}

private FileResult CreateZip(IEnumerable<FileContentResult> files)
{
    byte[] retVal = null;

    if (files.Any())
    {
        using (MemoryStream zipStream = new MemoryStream())
        {
            using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, false))
            {
                foreach (var f in files)
                {
                    var entry = archive.CreateEntry(f.FileDownloadName, CompressionLevel.Fastest);
                    using (var entryStream = entry.Open())
                    {
                        entryStream.Write(f.FileContents, 0, f.FileContents.Length);
                        entryStream.Close();
                    }
                }

                zipStream.Position = 0;
                retVal = zipStream.ToArray();
            }
        }
    }

    return File(retVal, MediaTypeNames.Application.Zip, "horta.zip");
}

Может кто-нибудь, пожалуйста, пролить свет на то, почему Windows говорит, что мой zip-файл недействителен, когда я дважды щелкаю по нему.
И наконец, я могу открыть его с помощью 7-Zip.

6 ответов

Решение

Вам нужно получить буфер MemoryStream через ToArray после удаления объекта ZipArchive. В противном случае вы получите поврежденный архив.

И обратите внимание, что я изменил параметры конструктора ZipArchive, чтобы он оставался открытым при добавлении записей.

Во время удаления ZipArchive происходит некоторая контрольная сумма, поэтому, если вы читали MemoryStream ранее, она все еще не завершена.

    private FileResult CreateZip(IEnumerable<FileContentResult> files)
    {
        byte[] retVal = null;

        if (files.Any())
        {
            using (MemoryStream zipStream = new MemoryStream())
            {
                using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
                {
                    foreach (var f in files)
                    {
                        var entry = archive.CreateEntry(f.FileDownloadName, CompressionLevel.Fastest);
                        using (BinaryWriter writer = new BinaryWriter(entry.Open()))
                        {                                   
                            writer.Write(f.FileContents, 0, f.FileContents.Length);
                            writer.Close();
                        }
                    }

                    zipStream.Position = 0;
                }
                retVal = zipStream.ToArray();
            }
        }

        return File(retVal, MediaTypeNames.Application.Zip, "horta.zip");
    }

Просто верните поток...

private ActionResult CreateZip(IEnumerable files)
{
    if (files.Any())
    {
        MemoryStream zipStream = new MemoryStream();
        using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, false))
        {
            foreach (var f in files)
            {
               var entry = archive.CreateEntry(f.FileDownloadName, CompressionLevel.Fastest);
               using (var entryStream = entry.Open())
               {
                   entryStream.Write(f.FileContents, 0, f.FileContents.Length);
                   entryStream.Close();
               }
           }

        }

        zipStream.Position = 0;
        return File(zipStream, MediaTypeNames.Application.Zip, "horta.zip");
    }

    return new EmptyResult();
}

Попробуйте изменить

using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, false))

в

using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))

При таком использовании архив вынужден записывать в поток, когда он закрыт. Однако если аргумент конструктора оставьте значение false, он также закроет основной поток.

Попробуйте добавитьdisposeперед возвратом потока, чтобы освободить ресурсы, используемые текущим экземпляромSystem.IO.Compression.ZipArchiveсорт.

       private FileResult CreateZip(IEnumerable<FileContentResult> files)
{
    byte[] retVal = null;

    if (files.Any())
    {
        using (MemoryStream zipStream = new MemoryStream())
        {
            using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create, true))
            {
                foreach (var f in files)
                {
                    var entry = archive.CreateEntry(f.FileDownloadName, CompressionLevel.Fastest);
                    using (BinaryWriter writer = new BinaryWriter(entry.Open()))
                    {                                   
                        writer.Write(f.FileContents, 0, f.FileContents.Length);
                        writer.Close();
                    }
                }

                zipStream.Position = 0;
            }

            archive.Dispose();
            retVal = zipStream.ToArray();
        }
    }

    return File(retVal, MediaTypeNames.Application.Zip, "horta.zip");

Я получил «Сжатая (заархивированная) папка ... недействительна». ошибка, потому что мои записи были названы с ведущим «/» перед ними. У некоторых zip-распаковщиков не было проблем с этим, но у Windows есть. Я решил это, удалив косую черту из имени записи (от «/file.txt» до «file.txt»).

Когда я добавил неправильное имя для записи, как в примере

var fileToZip = "/abc.txt";
ZipArchiveEntry zipFileEntry = zipArchive.CreateEntry(fileToZip);

Я получил ту же ошибку. После исправления имени файла теперь все в порядке.

Другие вопросы по тегам