ASP.NET CORE WEBAPI文件下載

最近要使用ASP.NET CORE WEBAPI用來下載文件,使用的.NET CORE 3.1。考慮以下場景:web

  1. 文件是程序生成的。
  2. 文件應該能兼容各類格式。
  3. 瀏覽器能夠感知進行下載。

準備

通過簡單的調研,獲得如下結論。c#

  • ASP.NET CORE 提供FileResult這種類型的ActionResult,能夠直接返回文件結果,不須要直接處理HttpResponse。
  • 經過Stream能夠直接返回文件流供瀏覽器下載。
  • FileStreamResult是FileResult的具體實現,返回值應該是此類對象。
  • Stream有多種類型,適合直接內存中生成文件對象的是MemoryStream。
    對目標有了基礎的瞭解,就能夠開始動手實現了。

實現

創建好ASP.NET CORE WEBAPI工程,把生成文件的代碼獨立出來一個函數。我這裏須要是下載一個CSV格式的文件,所以生成一個CSV文件。
對於磁盤上的文件,可使用FileStream對象,因爲我這裏須要運行中生成這個文件,須要使用MemoryStream。api

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();
}
  1. 請不要考慮裏面反射的相關內容,按照本身的邏輯生成CSV便可,我只是懶得改代碼而已。
  2. 代碼中使用到了一些新的語法特性,請注意對低版本的.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語句,再也不報相同錯誤。app

可是返回的文件長度一直是0,單步調試發現Writer執行完畢以後,stream返回的長度是0,內容實際上並無寫入,想起有一個Flush(),能夠添加以確保數據寫入。async

單步顯示stream長度有了,可是返回的長度仍是0。繼續單步調試發現Stream的Postion是停在文件結尾的,這個和直接開始讀取文件徹底不同,文件讀取通常是從開頭開始的,因而直接設置Postion爲0,問題解決。函數

下載可以成功了,可是文件名一直顯示的是隨機生成的,體驗不好。設置一下FileDownloadName便可。.net

核心代碼以下:調試

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調用,最後效果:code

總結

後來查了一些資料,總結了一下:

  • 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/

相關文章
相關標籤/搜索