C#.NET创建多条目Zip文件报错、压缩包损坏问题求解
C# ZipArchive 生成损坏压缩包问题修复
问题复现
从Blob存储拉取文档打包ZIP下载的接口,首次运行时在zar.CreateEntry(name)位置抛出异常:
System.IO.IOException: 'Entries cannot be created while previously created entries are still open.'
手动为条目写入流添加str.Close()调用后,接口可正常返回文件,但下载得到的压缩包用WinZip解压时报错unable to seek to beginning of Central Directory,压缩包无法正常打开。
问题根因
两个报错分别由两个代码逻辑错误导致:
- 条目流未释放:
ZipArchive在创建模式下同一时间仅允许打开一个可写条目流,上一个条目写入完成后如果没有释放对应流,直接创建下一个条目就会触发第一个IO异常。手动调用Close()虽然解决了这个问题,但写法存在资源泄漏风险。 - 流指针操作时机错误:这是压缩包损坏的核心原因。
ZipArchive在Dispose(即退出using代码块)时,才会将ZIP文件格式必需的中央目录元数据写入到底层流。原代码在ZipArchive的using块内部就执行outms.Seek(0, SeekOrigin.Begin),此时中央目录还未写入,后续ZipArchive写入元数据时流位置已经被改动,最终生成的ZIP文件中央目录位置非法,解压工具无法读取就会抛出对应错误。
正确实现代码
[HttpPost] public FileContentResult DownloadDocumentsByDocIDZIP(List<int> documentIDs) { List<Document> docs = new List<Document>(); foreach (int docId in documentIDs) { if (docId != 0) { docs.Add(documentService.GetDocumentByID(docId, false)); } } MemoryStream outms = new MemoryStream(); // 传入leaveOpen: true,避免ZipArchive释放时关闭底层MemoryStream using (ZipArchive zar = new ZipArchive(outms, ZipArchiveMode.Create, leaveOpen: true)) { foreach (Document docu in docs) { if (docu == null) continue; byte[] documentdata = documentService.DownloadDocumentData(docu.DocumentID); string fileName = docu.DocumentNiceName ?? docu.DocumentFileName; ZipArchiveEntry entry = zar.CreateEntry(fileName); // 用using块自动释放条目流,无需手动调用Close using (Stream entryStream = entry.Open()) using (MemoryStream ms = new MemoryStream(documentdata)) { ms.CopyTo(entryStream); } } // 禁止在此处移动流指针,此时ZipArchive尚未写入中央目录 } // ZipArchive完成所有内容写入、释放后,再移动流指针读取全部字节 outms.Seek(0, SeekOrigin.Begin); return File(outms.ToArray(), "application/zip", "documents.zip"); }
关键调整点
- 所有流对象都通过
using块包裹,自动完成资源释放,彻底避免条目流未关闭的异常,也无需手动调用Dispose/Close - 初始化
ZipArchive时显式指定leaveOpen: true,防止底层MemoryStream被意外关闭 - 将流指针重置操作移动到
ZipArchive的using块外部,保证ZIP中央目录等元数据完整写入后再读取流内容,避免压缩包结构损坏
内容的提问来源于stack exchange,提问作者Drikus Barnard
相关产品推荐
相关产品推荐

