ASP.NET Core 3.1 MVC アプリでファイルをダウンロードする方法を書きます。下の画像のようにファイル名とファイルの種類をブラウザにきちんと認識させるのが条件です。(画像は IE11 の例です。通知バーが正しく出ていることに注目してください)
.NET Framework MVC5 アプリでファイルをダウンロードする方法は、先の記事「MVC でファイルのダウンロード」に書きましたのでそちらを見てください。
ASP.NET Core 3.1 MVC の場合も基本的なことは .NET Framework MVC5 とほぼ同じで、FileResult Class を継承する VirtualFileResult, FileContentResult, FileStreamResult, PhysicalFileResult オブジェクトのいずれかを Controller のアクションメソッドで生成して返してやることになります。
VirtualFileResult, FileContentResult, FileStreamResult オブジェクトを生成するには File メソッドが使えます。File メソッドの第 1 引数の型に応じて生成されるオブジェクトが異なり、以下のようになります。
-
string 型: VirtualFileResult - コンテンツが既存のファイルとして提供される場合
-
byte[] 型: FileContentResult - コンテンツがバイト配列として提供される場合
-
stream 型: FileStreamResult - コンテンツがストリームとして提供される場合
上の 1 番目の「コンテンツが既存のファイルとして提供される場合」は File メソッドの第 1 引数にファイルの仮想バスを設定します(注:物理パスを指定する .NET Framework MVC5 の File メソッドと異なります)。ファイルはアプリケーションルート直下の wwwroot フォルダ下に置きます(そこ以外では見つからないというサーバーエラーになります)。例えば wwwroot/Files/test.pdf をダウンロードする場合は File メソッドの第 1 引数は "/Files/test.pdf" とします。
物理パスを指定したい場合は PhysicalFileResult クラスを使います。File メソッドを使わず明示的に PhysicalFileResult クラスを初期化して返してやる必要があるようです。
File メソッドの第 2 引数にはファイルの MIME タイプ、第 3 引数には拡張子を含むファイル名を設定します。そうすることにより応答ヘッダに Content-Type と Content-Disposition が適切に設定されます。ブラウザによって Content-Type, Content-Disposition のどちらでファイル名とファイルの種類を判断するかが異なりますので両方をきちんと設定するのは必須です。
.NET Framework MVC5 の File メソッドの場合、第 3 引数に設定するファイル名には US-ASCII 文字を使用しないと IE では文字化けしましたが、Core の File メソッドは RFC 6266 (RFC 2231/RFC 5987) に準拠した Content-Disposition ヘッダを生成するので文字化けの問題は回避できるようになりました。例えば第 3 引数に "日本語.pdf" という文字列を設定すると以下の画像にあるような Content-Disposition ヘッダが生成されます。
キャッシュコントロールは ResponseCacheAttribute で設定します。例えば [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] とすると、上の応答ヘッダの画像のとおり Cache-Control: no-store, no-cache / Pragma: no-cache となります。(注: .NET Framework MVC5 の Response.Cache は使えません)
以下に、順に PhysicalFileResult, VirtualFileResult, FileContentResult, FileStreamResult クラスを使った場合のサンプルコードを書いておきます。
PhysicalFileResult
任意のフォルダにある既存のファイルを物理パスを指定してダウンロードする場合です。File メソッドは使えないようなので PhysicalFileResult クラスを直接初期化して return します。Content-Disposition ヘッダは自力でコードを書いて設定します(RFC 6266 に準拠したい場合はそれも自力でコーディング要)。
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Hosting;
using MvcCoreApp.Models;
using Microsoft.AspNetCore.Http;
using System.IO;
namespace MvcCoreApp.Controllers
{
public class DownloadController : Controller
{
// Core では Server.MapPath が使えないことの��応
private readonly IWebHostEnvironment _hostingEnvironment;
public DownloadController(
IWebHostEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
[HttpGet("/downloadfile")]
[ResponseCache(Duration = 0,
Location = ResponseCacheLocation.None,
NoStore = true)]
public IActionResult DownloadFile()
{
// Content-Disposition ヘッダを設定
// (RFC 6266 対応してないので注意)
Response.Headers.Append("Content-Disposition",
"attachment;filename=test.pdf");
// アプリケーションルートの物理パスを取得
// wwwroot の物理パスは WebRootPath プロパティを使う
string contentRootPath =
_hostingEnvironment.ContentRootPath;
// ダウンロードするファイルの物理パス。アプリケーション
// ルート直下の Files フォルダの test.pdf とする
string physicalPath = contentRootPath + "\\" +
"Files\\" + "test.pdf";
return new PhysicalFileResult(physicalPath,
"application/pdf");
}
}
}
VirtualFileResult
wwwroot フォルダ下にある既存のファイルを仮想パスを指定してダウンロードする場合です。File メソッドが利用できます。
[HttpGet("/downloadfile")]
[ResponseCache(Duration = 0,
Location = ResponseCacheLocation.None,
NoStore = true)]
public IActionResult DownloadFile()
{
// wwwroot/Files フォルダ内の test.pdf を指定
string virtualPath = "/Files/test.pdf";
// RFC 6266 に準拠した Content-Disposition ヘッダを生成する
// ので第 3 引数のファイル名には日本語が使えます
return File(virtualPath, "application/pdf", "日本語.pdf");
}
FileContentResult
ダウンロードするコンテンツがバイト配列として提供される場合です。コンテンツを SQL Server からバイト配列として取得するような時に利用できそうです。
[HttpGet("/downloadfile")]
[ResponseCache(Duration = 0,
Location = ResponseCacheLocation.None,
NoStore = true)]
public IActionResult DownloadFile()
{
// 以下はバイト配列を取得するための単なるサンプル
string contentRootPath =
_hostingEnvironment.ContentRootPath;
string physicalPath = contentRootPath + "\\" +
"Files\\" + "test.pdf";
byte[] data = System.IO.File.ReadAllBytes(physicalPath);
return File(data, "application/pdf", "日本語.pdf");
}
FileStreamResult
ダウンロードするコンテンツがストリームとして提供される場合です。コンテンツを Web API から HttpClient を利用して取得するような時に利用できるでしょうか。
[HttpGet("/downloadfile")]
[ResponseCache(Duration = 0,
Location = ResponseCacheLocation.None,
NoStore = true)]
public IActionResult DownloadFile()
{
// 以下は stream を取得するための単なるサンプル
string contentRootPath =
_hostingEnvironment.ContentRootPath;
string physicalPath = contentRootPath + "\\" +
"Files\\" + "test.pdf";
FileStream stream =
new FileStream(physicalPath, FileMode.Open);
return File(stream, "application/pdf", "日本語.pdf");
}