在 WebAPI 里生成 csv zip 文件
Intro
前段时间在写一个导出 csv 压缩包的 API,踩了一些坑,记录一下同时分享一下避免大家踩坑
Sample
API 是导出一个 zip 压缩包,这个压缩包里一些 csv 文件
csv 用的是
CsvHelper
来导出的,zip 是用的 .NET 里自带的
ZipArchive
来实现的,完整的导出代码如下:
[HttpPost("download"
)
]
public
async
Task<IActionResult> DownloadCsv
(
)
{
var
books = Get();
var
memoryStream = new
MemoryStream();
using
(var
zip = new
ZipArchive(memoryStream, ZipArchiveMode.Create, true
))
{
foreach
(var
group
in
books
.GroupBy(t => new
{
t.Author
}))
{
var
entryName = $"{group
.Key.Author}
.csv"
;
var
entry = zip.CreateEntry(entryName);
await
using
var
writer = new
StreamWriter(entry.Open());
await
using
var
csv = new
CsvWriter(writer, new
CsvConfiguration(CultureInfo.InvariantCulture)
{
HasHeaderRecord = false
});
// register class map if necessary
// csv.Context.Register classMap< classMap>();
await
csv.WriteRecordsAsync(group
);
}
}
memoryStream.Seek(0
, SeekOrigin.Begin);
return
File(memoryStream, "application/zip"
, "books.zip"
);
}
说一说可能会踩到坑:
memory stream 首先不能 using,因为要写入到 response 里
ZipArchive
create 的时候需要指定
ZipArchiveMode.Create
并且,
leaveOpen
需要指定为
true
,原因同上,默认会 dispose stream
CsvHelper
要写入 entry 的时候可以直接用
ZipArchiveEntry
的
Open()
方法来获取对应的 stream,直接写入 entry 的 stream
ZipArchive
不要使用
using var zip = new ZipEntry(..);
这样的写法,这样写会有问题,会导致
ZipArchive
不会及时 dispose 导致 stream 数据是有问题的,使用
using {}
的形式,自己控制 using 的 scope
最后导出之前需要把 memory stream 重置到开始的位置,可以使用
memoryStream.Seek(0, SeekOrigin.Begin)
,否则也会导致导出异常
导出效果:
References
https://github.com/WeihanLi/SamplesInPractice/blob/main/AspNetCoreSample/Controllers/BooksController.cs#L39
https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.cs
https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs