CodeSnippet.Cn
代码片段
Csharp
架构设计
.NetCore
西班牙语
kubernetes
MySql
Redis
Algorithm
Other
Ubuntu
Linux
.NetMvc
VisualStudio
Python
Git
pm
WPF
java
Plug-In
分布式
CSS
微服务架构
JavaScript
DataStructure
Shared
.Net ZipArchive更新到基础流的方法
0
Csharp
.NetCore
小笨蛋
发布于:2022年05月06日
更新于:2022年05月07日
199
#custom-toc-container
### 简介 `ZipArchive`是.Net中常用的ZIP文档操作类,可以用来对ZIP压缩文档进行各种操作 例如,我们用它来解压`sample.zip`文件: ```csharp using (FileStream fs = new FileStream("sample.zip", FileMode.Open)) { using (ZipArchive zipArchive = new ZipArchive(fs, ZipArchiveMode.Read)) { zipArchive.ExtractToDirectory("extract"); } } ``` 或者在ZIP文件中创建一个新文件: ```csharp using (FileStream fs = new FileStream("sample.zip", FileMode.Open)) { using (ZipArchive zipArchive = new ZipArchive(fs, ZipArchiveMode.Update)) { ZipArchiveEntry entry = zipArchive.CreateEntry("NewFile.txt"); using (StreamWriter sw = new StreamWriter(entry.Open())) { sw.WriteLine("hello world"); } } } ``` ### 另一个场景 假设我们需要从内存中获取ZIP文件(byte[]形式),希望在里面创建/重命名/删除某些文件,随后再将操作写回到内存中(也是byte[]),例如从远端下载二进制ZIP,进行操作后再保存,应当如何操作呢。 你可能会不假思索地写出如下代码: ```csharp static byte[] AddNewFile(byte[] data) { byte[] res; using (MemoryStream ms = new MemoryStream()) { ms.Write(data); ms.Position = 0; using (ZipArchive zipArchive = new ZipArchive(ms, ZipArchiveMode.Update)) { ZipArchiveEntry entry = zipArchive.CreateEntry("NewFile2.txt"); using (StreamWriter sw = new StreamWriter(entry.Open())) { sw.WriteLine("This sample file will no write to data"); } } res = new byte[ms.Length]; ms.Position = 0; ms.Read(res, 0, res.Lenght); return res; } } ``` ### 问题 运行一下,会提示`MemoryStream`被关闭了: ![图片alt](/uploads/images/20220506/214319-c0d3f322466e407c9ffb541d51c7e3e3.png '代码片段:Www.CodeSnippet.Cn') 这是个很容易被忽略,但又非常重要的基础知识:.net中许多操作`BaseStream`的类在`Dispose`时都会关闭`BaseStream`,例如`StreamReader`、`StreamWriter`等等,当然`ZipArchive`也不例外。具体可以参考这篇文章:[MSDN-CA2202:不要多次释放对象](https://msdn.microsoft.com/zh-cn/library/ms182334.aspx "MSDN-CA2202:不要多次释放对象") 不过呢,`StreamReader`、`StreamWriter`这些类都没有非托管资源,我们没有必要去释放他们。 再回到我们的问题,既然`MemoryStream`在`ZipArchive`被释放时关闭了,我们自然也就无法取出数据。那么我们能不能在`ZipArchive`释放之前取出数据呢。 修改后的代码: ```csharp static byte[] AddNewFile(byte[] data) { byte[] res; using (MemoryStream ms = new MemoryStream()) { ms.Write(data); ms.Position = 0; using (ZipArchive zipArchive = new ZipArchive(ms, ZipArchiveMode.Update)) { ZipArchiveEntry entry = zipArchive.CreateEntry("NewFile2.txt"); using (StreamWriter sw = new StreamWriter(entry.Open())) { sw.WriteLine("This sample file will no write to data"); } int nowPos = (int)ms.Position; res = new byte[ms.Length]; ms.Position = 0; ms.Read(res, 0, res.Length); ms.Position = nowPos; } } return res; } ``` ### 新的问题 这次我们尝试在`ZipArchive`释放之前取出数据,运行一下,将结果写到文件中,却发现没有新文件`NewFile2.txt`。 ![图片alt](/uploads/images/20220506/214410-8e30493122ef4beea40df7b08776a2bd.png '代码片段:Www.CodeSnippet.Cn') 这让问题陷入了困境,既然能想到的方法都不能成功,不如查看一下`ZipArchive`的底层实现 官方源码:[.Net-ZipArchive源码](https://source.dot.net/#System.IO.Compression/System/IO/Compression/ZipArchive.cs,6c9fcdfe08f90323 ".Net-ZipArchive源码") 有意思的地方来了,`ZipArchive`内部有一个名为`WriteFile`的私有方法([616行](https://source.dot.net/#System.IO.Compression/System/IO/Compression/ZipArchive.cs,616 "616行"))。此方法会更新我们提供的BaseStream,也就是MemoryStream。而这个方法只在Dispose方法([199行](https://source.dot.net/#System.IO.Compression/System/IO/Compression/ZipArchive.cs,199 "199行"))中被调用 下面是`Dispose`方法的代码 ```csharp protected virtual void Dispose(bool disposing) { if (disposing && !_isDisposed) { try { switch (_mode) { case ZipArchiveMode.Read: break; case ZipArchiveMode.Create: case ZipArchiveMode.Update: default: Debug.Assert(_mode == ZipArchiveMode.Update || _mode == ZipArchiveMode.Create); WriteFile(); break; } } finally { CloseStreams(); _isDisposed = true; } } } ``` 可以看到,在`ZipArchive`释放时,会先调用`WriteFile`来将改动更新基础流,然后立刻关闭基础流。 如果我们提供的是`FileStream`,这会是非常好的做法:当我们释放`ZipArchive`时,会立刻保存改动并关闭`FileStream`。但我们现在使用的是`MemoryStream`,这让问题变得非常棘手,因为我们没有任何方法让它更新`Stream`,除非`Dispose`它,但这又会关闭我们的`MemoryStream`,使得我们无法取出数据。 ### 解决方法 至此,似乎只有一种解决方法:通过反射来调用私有的`WriteFile`来更新`Stream`,随后取出数据,再释放`ZipArchive`。 `InvokeWriteFile`方法用于调用ZipArchive的私有方法`WriteFile` ```csharp static void InvokeWriteFile(ZipArchive zipArchive) { foreach (MethodInfo method in zipArchive.GetType().GetRuntimeMethods()) { if (method.Name == "WriteFile") { method.Invoke(zipArchive, new object[0]); } } } static byte[] AddNewFile(byte[] data) { byte[] res; using (MemoryStream ms = new MemoryStream()) { ms.Write(data); ms.Position = 0; using (ZipArchive zipArchive = new ZipArchive(ms, ZipArchiveMode.Update)) { ZipArchiveEntry entry = zipArchive.CreateEntry("NewFile2.txt"); using (StreamWriter sw = new StreamWriter(entry.Open())) { sw.WriteLine("This sample file will write to data"); } InvokeWriteFile(zipArchive); int nowPos = (int)ms.Position; res = new byte[ms.Length]; ms.Position = 0; ms.Read(res, 0, res.Length); ms.Position = nowPos; } } return res; } ``` 写入成功! ![图片alt](/uploads/images/20220506/214544-ca6060def70c4b9aa3f6dd03946e0196.png '代码片段:Www.CodeSnippet.Cn') ### .net Core中的应用 ```csharp public async Task
DownloadZipTask(params Entity[] requests) { var dict = new Dictionary
(requests.Count()); //省略获取文件流的步骤 byte[] content; using (var ms = new MemoryStream()) { using (var zip = new ZipArchive(ms, ZipArchiveMode.Create, true)) { foreach (var (id, xmlStream) in dict) { //压缩文件内创建一个文件名为“导入{id}.xml”,流是什么文件格式就用什么格式,此处我的是xml格式 var entry = zip.CreateEntry($"导入{id}.xml"); using (var sw = entry.Open()) { var xmlBytes = xmlStream.ToArray(); //文件流转换成字节 await sw.WriteAsync(xmlBytes, 0, xmlBytes.Length); //将文件的字节写到xml中 } } //没必要重新计算大小 //foreach (var method in zip.GetType().GetRuntimeMethods()) //重新计算压缩文件的大小 //{ // if (method.Name == "WriteFile") // { // method.Invoke(zip, new object[0]); // } //} } //下面一定要写在using ZipArchive外面,否则压缩文件会被损坏 int nowPos = (int)ms.Position; content = new byte[ms.Length]; ms.Position = 0; ms.Read(content, 0, content.Length); ms.Position = nowPos; } return content; } ``` ```csharp public async Task
DownloadZip([FromBody]QueryParams args) { var dto = await Mediator.Send(args); HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition"); return File(dto.Content, "application/octet-stream", "test.zip"); } ```
这里⇓感觉得写点什么,要不显得有点空,但还没想好写什么...
返回顶部
About
京ICP备13038605号
© 代码片段 2025