最近要使用ASP.NET CORE WEBAPI用來下載檔案,使用的.NET CORE 3.1,考慮如下場景:
- 檔案是程式生成的,
- 檔案應該能兼容各種格式,
- 瀏覽器可以感知進行下載,
準備
經過簡單的調研,得到以下結論,
- ASP.NET CORE 提供FileResult這種型別的ActionResult,可以直接回傳檔案結果,不需要直接處理HttpResponse,
- 通過Stream可以直接回傳檔案流供瀏覽器下載,
- FileStreamResult是FileResult的具體實作,回傳值應該是此類物件,
- Stream有多種型別,適合直接記憶體中生成檔案物件的是MemoryStream,
對目標有了基礎的了解,就可以開始動手實作了,
實作
建立好ASP.NET CORE WEBAPI工程,把生成檔案的代碼獨立出來一個函式,我這里需要是下載一個CSV格式的檔案,因此生成一個CSV檔案,
對于磁盤上的檔案,可以使用FileStream物件,由于我這里需要運行中生成這個檔案,需要使用MemoryStream,
using var stream = new MemoryStream();
using var writer = new StreamWriter(stream);
//生成標題
var propCollection = ttype.GetProperties();
foreach (var n in propCollection)
{
writer.Write(n.Name);
writer.Write(",");
}
writer.WriteLine();
//生成內容
foreach (var item in res)
{
foreach (var n in propCollection)
{
writer.Write(Convert.ToString(n.GetValue(item)));
writer.Write(",");
}
writer.WriteLine();
}
- 請不要考慮里面反射的相關內容,按照自己的邏輯生成CSV即可,我只是懶得改代碼而已,
- 代碼中使用到了一些新的語法特性,請注意對低版本的.NET不一定適用,
直接回傳Stream物件給Controller處理,處理代碼如下:
var res = await info.GetAllQueryResult();
var actionresult = new FileStreamResult(res, new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("text/csv"));
return actionresult;
CSV的Content-Type是text/csv,如果下載別的檔案,請自行查詢MIME格式,
除錯
直接執行上面的代碼,直接報錯“無法讀取已經關閉的流”,猜測是離開using陳述句塊的時候,stream自動被關閉了,改動很簡單,去掉using陳述句,不再報相同錯誤,
但是回傳的檔案長度一直是0,單步除錯發現Writer執行完畢之后,stream回傳的長度是0,內容實際上并沒有寫入,想起有一個Flush(),可以添加以確保資料寫入,
單步顯示stream長度有了,但是回傳的長度還是0,繼續單步除錯發現Stream的Postion是停在檔案結尾的,這個和直接開始讀取檔案完全不一樣,檔案讀取一般是從開頭開始的,于是直接設定Postion為0,問題解決,
下載能夠成功了,但是檔案名一直顯示的是隨機生成的,體驗很差,設定一下FileDownloadName即可,
核心代碼如下:
public async Task<Stream> GetAllQueryResult()
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
//生成標題
var propCollection = ttype.GetProperties();
foreach (var n in propCollection)
{
writer.Write(n.Name);
writer.Write(",");
}
writer.WriteLine();
//生成內容
foreach (var item in res)
{
foreach (var n in propCollection)
{
writer.Write(Convert.ToString(n.GetValue(item)));
writer.Write(",");
}
writer.WriteLine();
}
writer.Flush();
stream.Position = 0;
return stream;
}
[HttpPost("file")]
[ProducesResponseType(typeof(FileResult), Status200OK)]
public async Task<FileResult> Download()
{
var info = new Info();
var res = await info.GetAllQueryResult();
var actionresult = new FileStreamResult(res, new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("text/csv"));
actionresult.FileDownloadName = "Carinfos.csv";
//Response.ContentLength = res.Length;
return actionresult;
}
使用swagger呼叫,最后效果:

總結
后來查了一些資料,總結了一下:
- MemoryStream如果使用using陳述句,會在離開代碼塊的時候自動關閉,實際上ASP.NET CORE會自動處理關閉的事項,不需要使用using陳述句,
- 由于生成檔案的程序是從檔案流的開頭一直進行到末尾的,因此向請求端回傳結果時,應當重置Stream的游標,從0開始傳輸,
- 記得在使用writer之后使用Flush()以確保資料有寫入,
- 如果不確定檔案格式,可以直接回傳MIME值為application/oct-stream,
- 設定FileStreamResult的FileDownloadName屬性可以修改檔案的默認名稱,
- (可選)可以通過設定Response.ContentLength來設定檔案的長度,
參考資料:
https://darchuk.net/2019/05/31/asp-net-core-web-api-returning-a-filestream/
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/44875.html
標籤:.NET Core
