在 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