.NET6 Web Api 檔案下載同時取得檔名

.NET6, Download File, Content-Disposition, ContentDisposition

引言

為工作紀錄。找到一個比較標準的答案在下載檔案時同時也取得檔名。

主要參考文章

下載檔案

下載檔案同時送回檔名:Content-Disposition

這個Content-Disposition為一個 Response Header 屬性之一。用來把附件檔名等資訊送回去前端,此機制在歷經多年後已變成標準了。標準格式如下例:

Content-Disposition: attachment; filename="theFileName.ext"

因為多國語語有字型編碼的問題,固再加入一個 "filenam*" 屬性並有UTF-8與URL編碼,如下例子:

Content-Disposition: attachment; filename="MinIO __.docx"; filename*=UTF-8''MinIO%20%E8%A9%95%E4%BC%B0.docx

可用decodeURI指令來解碼得出檔名,如下圖例:

參考關鍵字文件,更多說明請膜拜google大神。這裡以紀錄為主。

程式碼紀錄

後端:Web API in .NET6

using Microsoft.AspNetCore.Mvc;
namespace YourProject.Controllers;

[ApiController]
[Route("api/[controller]/[action]")]
public class FileController : ControllerBase
{
  [HttpPost]
  public IActionResult DownloadFile()
  {
    FileInfo fi = new FileInfo(@"Template/MinIO 評估.docx");
    byte[] blob = System.IO.File.ReadAllBytes(fi.FullName);
    return File(blob, System.Net.Mime.MediaTypeNames.Application.Octet, fi.Name);
    //※ 其中 FileContentResult 第3個參數會產生 Content-Disposition 的 Response Header 把檔名編碼後送到前端。
  }
}

前端:React.v18

import { saveAs } from 'file-saver'

function handleDownloadFile() {
    const url = 'api/File/DownloadFile'
    const options = {
      method: 'POST',
    };

    let fileName = 'unknown.bin'
    fetch(url, options)
      .then(resp => {
        if (!resp.ok) throw new Error('Network response was not ok.');

        // 解析附件檔名
        const contentDisposition = resp.headers.get('content-disposition') as string
        fileName = decodeURI(contentDisposition.split("filename*=UTF-8''")[1])
        return resp.blob();
      })
      .then(blob => {
        saveAs(blob, fileName)
      });
  }

組態:可能會有 CORS (Cross-Origin Resource Sharing)預設限制把 Content-Disposition 過濾掉,必須設定成可以通過:

Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
  options.AddDefaultPolicy(policy =>
    policy.WithExposedHeaders("Content-Disposition") 
    //※ 讓 FileContentResult 可以回傳檔名。預設被過濾掉。
  ));

...省略...

var app = builder.Build();
...省略...
app.UseRouting();
app.UseCors();
...省略...

EOF

Last updated