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.");
}
}
}