在 WebAPI 里生成 csv zip 文件
Intro
前段时间在写一个导出 csv 压缩包的 API,踩了一些坑,记录一下同时分享一下避免大家踩坑
Sample
API 是导出一个 zip 压缩包,这个压缩包里一些 csv 文件
csv 用的是
[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.RegisterClassMap<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