WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

ASP.NET Core MVC でファイルダウンロード

by WebSurfer 20. January 2020 12:09

ASP.NET Core 3.1 MVC アプリでファイルをダウンロードする方法を書きます。下の画像のようにファイル名とファイルの種類をブラウザにきちんと認識させるのが条件です。(画像は IE11 の例です。通知バーが正しく出ていることに注目してください)

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 引数の型に応じて生成されるオブジェクトが異なり、以下のようになります。

  1. string 型: VirtualFileResult - コンテンツが既存のファイルとして提供される場合
  2. byte[] 型: FileContentResult - コンテンツがストリームとして提供される場合
  3. 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 UploadController(
                      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");
}

Tags: , ,

CORE

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  July 2020  >>
MoTuWeThFrSaSu
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar