WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

MVC でファイルのダウンロード

by WebSurfer 2018年12月12日 16:06

ASP.NET MVC アプリケーションでファイルをダウンロードするのはどうすればいいかということを備忘録として書いておきます。(Web Forms アプリの場合は先の記事「ダウンロードは HTTP ハンドラで」を見てください)

IE11 の通知バー

上の画像のように通知バーを表示し(IE11 の場合です)、ファイル名とファイルの種類をブラウザにきちんと認識させるのが条件です。

基本的には以下のアクションメソッド FileDownload のようにします。

public ActionResult FileDownload()
{
    string fileName = Server.MapPath("~/Files/test.pdf");
    return File(fileName, "application/pdf", "test.pdf");
}

上のコードの return File(...); のヘルパーメソッド File は、FileResult クラスを継承した FilePathResult, FileContentResult, FileStreamResult 派生クラスのいずれかを呼び出します。

それらの使い分けは以下の通りです。

  1. FilePathResult: コンテンツが既存のファイルとして提供される。
  2. FileStreamResult: コンテンツがストリームとして提供される。
  3. FileContentResult: コンテンツがバイト配列として提供される。

上記のどれを呼び出すかは return File(...); の引数によります。詳しくは Microsoft のドキュメント Controller.File Method を見てください。

上のコード例では第一引数が文字列(ファイルの物理パス)なので FilePathResult が呼び出されます。

FilePathResult は Web Forms アプリでもファイルをダウンロードする際に使われている TransmitFile メソッドを呼び出します。TransmitFile メソッドは、FilePathResult の第 1 引数に指定されたファイル名を受けて、そのファイルをメモリにバッファリングせずに、HTTP 応答出力ストリームに直接書き込みます。

FileStreamResult は、第 1 引数に指定されるストリームから FileStream.Read メソッドでバイト列を取得し、それを HttpResponse.OutputStream.Write メソッドを使って出力ストリームに書き出すという操作を行います。

FileContentResult は、第 1 引数に指定されるバイト列を直接 HttpResponse.OutputStream.Write メソッドを使って出力ストリームに書き出すという操作を行います。

ファイル名とファイルの種類をブラウザに認識させ、上の画像のような通知を表示させるには、上のサンプルコードで return File(...); の第 2, 3 引数に順にファイルの MIME タイプ、拡張子を含むファイル名を設定します。そうすることにより、応答ヘッダに Content-Type と Content-Disposition が適切に設定されます。

なお、第 2 引数に設定するファイル名には US-ASCII 文字を使用しないと IE では文字化けするので注意してください。先の記事「ダウンロードファイル名の文字化け」に書いた方法でどのように対応できるかは未調査です。

キャッシュコントロールは Web Forms アプリと同様に HttpResponse.Cache で取得できる HttpCachePolicy オブジェクトを使って可能です。

例えば、上の FileDownload アクションメソッドに以下のコードを追加してやれば、

Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetExpires(DateTime.Now.ToUniversalTime());
Response.Cache.SetMaxAge(new TimeSpan(0, 0, 0, 0));

以下の画像の応答ヘッダのように Cache-Control: no-cache, Pragma: no-cache, Expires: -1 が設定されます。

応答ヘッダ

File メソッドの第 2, 3 引数に設定した MIME タイプとファイル名により、Content-Type と Content-Disposition が適切に設定されているところにも注目してください。

Tags:

Upload Download

チャンク形式でダウンロード

by WebSurfer 2016年12月14日 23:49

ASP.NET Web Forms アプリにおいて、ファイルをチャンク形式エンコーディングしてブラウザにダウンロードするサンプルを備忘録として書いておきます。

Fiddler で見た応答

チャンク形式エンコーディングとは HTTP/1.1 で定義されている方式で、送信したいデータを任意のサイズのチャンク(塊)に分割し、各々のチャンクにサイズ情報を付与するエンコード方式です。

メリットは、送信するデータを動的に作成していて、作成中は全体のサイズが分からないが、部分的にでも作成でき次第送信を始められるというところにあるようです。(一旦全データをバッファして全体のサイズを調べ、Content-Length に設定するということをしなくても済みます)

サーバーにある既存のファイルをダウンロードするような場合はファイルのサイズは分かっていますので、チャンク形式エンコーディングせずに Content-Length を設定してダウンロードする方がよさそうです。(なので、自分的にはチャンク形式エンコーディングの使い道はあまりなさそうですが、せっかく調べたので書いておきます)

ASP.NET Web Forms アプリでは、HttpResponse.OutputStream にチャンクを書き込んだら Flush することでそのチャンクがクライアント(ブラウザ)に送信されます。

具体的には以下のコードのようにします。Sig552T8.jpg は 30,903 バイトの jpeg 画像ファイルで、以下の .aspx ページをブラウザから要求すると、その画像データを 10,000 バイトずつチャンクに分けて送信するようになっています。

この記事の一番上の画像を見てください。Fiddler を使ってサーバーからの応答をキャプチャしたものですが、応答ヘッダの赤線で示した部分にチャンク形式エンコーディングになっていること、コンテンツの最初の部分 32 37 31 30(UTF-8 で 2710 ⇒ 10 進数に直すと 10000)に最初に送信されたチャンクのサイズが示されていることが分かります。

<%@ Page Language="C#" %>
<%@ Import Namespace="System.IO" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
  protected void Page_Load(object sender, EventArgs e)
  {
    string folder = "~/images/";
    string filename = "Sig552T8.jpg";
    string path = Server.MapPath(folder + filename);
    FileInfo fileInfo = new FileInfo(path);

    if (fileInfo.Exists)
    {
      int chunkSize = 10000;
      Byte[] buffer = new Byte[chunkSize];
      Response.Clear();   

      using (FileStream stream = File.OpenRead(path))
      {
        long length = stream.Length;
        Response.ContentType = "image/jpeg";
        Response.AddHeader("Content-Disposition", 
                "attachment; filename=" + fileInfo.Name);

        // ここで Flush しても通知バーは表示されない。以下のコ
        // ードのコメントアウトを外すとそれが確認できます。
        // Response.Flush();
        // System.Threading.Thread.Sleep(10000);

        while (length > 0 && Response.IsClientConnected)
        {
          int lengthRead = stream.Read(buffer, 0, chunkSize);
          Response.OutputStream.Write(buffer, 0, lengthRead);

          // ここでの最初の Flush で通知バーが表示される
          Response.Flush();
          length -= lengthRead;

          // chunked ダウンロードされていることを確認するため
          // 試験的に入れたコード。コメントアウトを外すとここ
          // で 5 秒待つ。
          // System.Threading.Thread.Sleep(5000);
        }
      }

      // <!DOCTYPE html ... 以下の html ソースをダウンロード
      // させないために設定
      Response.SuppressContent = true;
    }        
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    </form>
</body>
</html>

ブラウザ上ではどういう動きになるかと言うと、IE9 を使った場合ですが、上記コードの中の while ループの最初の Flush で以下の画像のように通知バーが表示されます。

IE9 の通知バー

上の通知バーの[ファイルを開く(O)]または[保存(S)]をクリックすると通知バーは以下の画像のように変わります。(注:Thread.Sleep(5000) のコメントアウトを外して試してください)

ダウンロード中の通知バー

その後、最初に表示された通知バーで[ファイルを開く(O)]をクリックしていた場合はダウンロードが完了すると画像 Sig552T8.jpg がブラウザ上に表示されます。

最初に表示された通知バーで[保存(S)]をクリックしていた場合はダウンロードが完了すると通知バーは以下の画像のように変わります。

ダウンロード完了の通知バー

[ファイルを開く(O)]または[フォルダーを開く(P)]でダウンロードした画像ファイルを確認できます。

Tags:

Upload Download

ダウンロードの際の拡張子

by WebSurfer 2016年6月12日 15:27

以下は Internet Exploler (IE) に限った話ですのでご注意ください。

IE でファイルをダウンロードする際、応答ヘッダに Content-Type のみが指定されていて、Content-Disposition が指定されてない場合、ファイルの拡張子はどうなるかという話を書きます。

レジストリでの拡張子設定

簡単に言うと、Microsoft の Support の記事「ファイルのダウンロードダイアログで表示されるファイル名の命名規則」の「詳細」のセクションの 2 に書いてありますように、レジストリの HKEY_CLASS_ROOT\MIME\Database\Content Type の設定によります。

具体例を書くと以下の通りです。

上の画像はレジストリの Content Type が image/tiff の拡張子の設定です。自分の開発マシン(Vista SP2 32-bit)ではデフォルトで Extension のデータは .tif になっていました。

開発マシンのローカル IIS7 に設定した images フォルダに、内容は全く同じで拡張子のみ .tiff と .tif と異なる 2 つの tiff 画像ファイルを作り、以下のように a 要素で直リンクします。それをブラウザに表示してリンクをクリックするとどうなるでしょうか?

<a href="/images/tiffdocument.tiff">tiffdocument.tiff</a>

<a href="/images/tiffdocument.tif">tiffdocument.tif</a>

実際に 2 つのファイルの拡張子は .tiff / .tif と異なるのですが、IIS7 がそれらの画像を取得してダウンロードする際、応答ヘッダに含まれる Content-Type はいずれの場合も image/tiff と設定します。(注:image/tif では IE が認識できません)

Content Type: image/tiff を受信した IE のあるクライアント PC のレジストリ設定は、上の画像が示すように image/tiff の Extension のデータが .tif となっているので、ダウンロードされた画像ファイルの名前.拡張子はどちらのリンクも tiffdocument.tif になります。

ちなみに、上の画像のレジストリの Extension のデータ .tif を .tiff に変更すると、ダウンロードされたファイルの拡張子は .tiff になります。

ダウンロードされたファイルのファイル名.拡張子を実際のファイルの通りにするには、サーバー側で Content-Disposition ヘッダでファイル名.拡張子をきちんと指定すれば、IE の場合は指定したとおりになります。

ただし、先の記事「ダウンロードの際の拡張子 と MIME Type の指定」に書きましたように、Content-Type の方を優先するブラウザもあります。

なので、ダウンロードするための HTTP ハンドラ等を作る際は、Content-Type と Content-Disposition の両方を正しく設定するように注意した方がよさそうです。

なお、プログラムで Content-Type を設定する際は image/tif ではなくて image/tiff としないと IE は認識しませんので注意してください。image/jpg も同様にダメで image/jpeg としないと IE は認識しません。

Tags: ,

Upload Download

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar