WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

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

by WebSurfer 20. December 2018 11:27

MVC のアクションメソッドを使ってファイルをチャンク形式でエンコーディングしてブラウザにダウンロードする方法を書きます。

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

チャンク形式エンコーディングせず、普通に(Content-Length を設定して)ダウンロードする方法については先の記事「MVC でファイルのダウンロード」に書きましたのでそちらを見てください。

チャンク形式エンコーディングでダウンロードするには、その記事で紹介したようなヘルパーメソッド File は使えませんので別の手段を考えることになると思います。

結局は、(1) HttpResponse オブジェクトを取得、(2) それから OutputStream プロパティを使って出力ストリームを取得、(3) コンテンツをチャンクに分割して Write メソッドでストリームに書き込む、(4) Flush メソッドでクライアントに送信する、(5) 全チャンクを送信するまで (3) と (4) の操作を繰り返す・・・ということになると思います。

MVC のアクションメソッドでは Controller.Response プロパティで HttpResponse オブジェクトを取得できますので、それを使って上記 (1) ~ (5) の操作を行うことができます。

そのコードを以下に書いておきます。結局は Web Forms アプリの記事「チャンク形式でダウンロード」のコードとほぼ同じですが・・・

public void ChunkedDownload()
{
  string folder = "~/Files/";
  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 = System.IO.File.OpenRead(path))
    {
      long length = stream.Length;
      Response.ContentType = "image/jpeg";
      Response.AddHeader("Content-Disposition",
              "attachment; filename=" + fileInfo.Name);

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

        Response.Flush();
        length -= lengthRead;
      }
    }
  }            
}

アクションメソッドの戻り値は ActionResult である必要はなく void にできることに注意してください。

また、上のコードで最後を示す長さ 0 のチャンクも送信されます。(Fiddler で最後のバイト列が ... 0D 0A 30 0D 0A 0D 0A となっているのを確認しました)

もう一つ、MVC アプリでも HTTP ジェネリックハンドラ(.ashx ファイル)は使えますので、アクションメソッドを使わなくても、HTTP ジェネリックハンドラに同様な機能を実装することは可能です。

Tags:

Upload Download

ASP.NET Web API の認証

by WebSurfer 17. December 2018 15:40

Visual Studio 2013 / 2015 の Web API テンプレートで、認証を「個別のユーザーアカウントカウント」として自動生成させた Web API アプリの認証は、デフォルトで、トークンを要求ヘッダに含めてサーバーに送信するトークンベースになります。(MVC テンプレートは不明。Core は全く不明)

ベアラトークン

上の画像は Web API に要求をかけたときの要求ヘッダを Fiddler で表示したものです。赤枠で示した部分が認証に使われるトークンです。(画像には認証クッキーも含まれていますが、その理由等については後述します)

(注:クッキーではなくトークンを使う理由はセキュリティ (CSRF) 対策だそうです。MVC アプリではビューに Html ヘルパーの AntiForgeryToken メソッドを、アクションメソッドに [ValidateAntiForgeryToken] 属性を付与して CSRF 対策ができますが、Web API ではそういう手段が使えませんから)

テンプレートで自動生成されたそのままの状態でアプリを実行してトークンが送られるわけではないです。トークンを送って認証が通るようにする手順と注意事項を以下に書いておきます。

トークンによる認証の仕組みについては Microsoft の文書 Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2 に詳しく書いてありますので、それを読むことをお勧めします。

その記事からリンクが張ってある GitHub のサイトから完全なサンプルが入手できます。それを試してもらってもいいのですが、そのサンプルには SSL 関係や knockout.js など Web API の認証とは直接関係のない部分もあって分かり難いところがあるかもしれません。(注:運用上は SSL の実装は必須です。knockout.js は必須ではありませんが)

なので、以下に Visual Studio 2015 の Web API テンプレート作成したアプリに、追加で何を実装すればトークンを入手でき、そしてトークンをどのように送信すれば認証が通るかだけを書いておきます。

事前に、ASP.NET Identity がユーザー情報を取得する LocalDB に email と password を登録し有効なユーザーアカウントを作成しておく必要があります。(その手順は本題とは直接関係ないので割愛します。手抜きでスミマセン)

トークンの入手は、ビューに以下のようなスクリプトを追加し、jQuery ajax を使って登録済みの email と password をトークンエンドポイント /Token に POST してやります。email と password が有効であれば、応答の JSON 文字列にトークンが含まれて戻ってきます。そのトークンを sessionStorage に保存します。

var tokenKey = 'accessToken';

function getToken() {
    var email = document.getElementById("email").value;
    var password = document.getElementById("password").value;

    var loginData = {
        grant_type: 'password',
        username: email,
        password: password
    };

    $.ajax({
        type: "POST",
        url: "/Token",
        data: loginData,
        success: function (data) {
          sessionStorage.setItem(tokenKey, data.access_token);
        },
        error: function (jqXHR, textStatus, errorThrown) {
            // ・・・中略・・・
        }
    });
}

トークンの送信は、上の getToken メソッドで sessionStorage に保存したトークンを取得し、以下のように要求ヘッダの Authorization に設定して送信してやります。それで認証が通って期待した応答が返ってきます。

function apiHeroesGet() {
    var token = sessionStorage.getItem(tokenKey);
    var headers = {};
    if (token) {
        headers.Authorization = 'Bearer ' + token;
    }

    $.ajax({
        type: "GET",
        url: "/api/values",
        headers: headers,
        success: function (data, textStatus, jqXHR) {
            // ・・・中略・・・
        },
        error: function (jqXHR, textStatus, errorThrown) {
            // ・・・中略・・・
        }
    });
}

主な話は以上ですが、追加で 2, 3 気になったことを書いておきます。

(1) トークンエンドポイント /Token

上のスクリプトの getToken メソッドの email と password の送信先 /Token は、Startup.Auth.cs の ConfigureAuth メソッドで OAuthAuthorizationServerOptions クラスを初期化するときに TokenEndpointPath プロパティに設定されます。

自動生成されたコードの中���アクションメソッドだと思って探したけれど、見つからないので焦ったというのは内緒です。(笑)

(2) 認証クッキーの発行

上の画像で認証クッキーも発行されていますが、それは上のスクリプトの getToken メソッドでトークンを発行する際に同時に発行されます。

デフォルトで、Startup.Auth.cs の ConfigureAuth メソッドには app.UseCookieAuthentication(...) メソッドが含まれていることに注意してください。

MVC 側のアクションメソッドの認証には、この認証クッキーが使用されます。例えば MVC 側のアクションメソッドに [Authorize] を付与してやると、認証クッキーなしでは 401 応答が返ってきます。

401 応答ではなく、302 応答を返してログインページにリダイレクトするには、引数の CookieAuthenticationOptions オブジェクトの LoginPath プロパティにログインページの URL を new PathString("/Account/Login") というように指定してやります。

(3) AuthenticationType の設定

UseCookieAuthentication メソッドの引数 CookieAuthenticationOptions オブジェクトの AuthenticationType を設定すると、トークンエンドポイント /Token に email と password を送信しても認証クッキーは発行されなくなります。(なお、トークンは発行されます)

認証クッキーは MVC 側でのログインだけで発行したい場合は、AuthenticationType を設定すればよさそうです。そのことが書いてあるドキュメントは見つけられなかったのですが、そのような目的のためにそうなっているような気がします。

AuthenticationType に設定されるのは単なる文字列で、何かの識別用に使っているらしいです。これをきちんと指定しないとログアウトできないなどの問題が出るという stackoverflow の記事を見かけました。(自分が Web API テンプレートのアプリで試した限りではそういう問題は出なかったですが・・・)

(4) トークンによる認証の強制

デフォルトでは Web API 側の認証はトークンになるのは、WebApiConfig.cs にある以下のコードによります。

config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(
                OAuthDefaults.AuthenticationType));

前者のメソッドでホスト(IIS と MVC) による認証は無視され、Web API には匿名アクセスとなるらしいです。後者のメソッドで HostAuthenticationFilter によりベアラトークンによる認証が行われるようになるそうです。

ちなみに、上の 2 行をコメントアウトすると認証クッキーだけで認証が通ってしまうようになります。

詳しくは、Microsoft の文書 Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2 の Configuring Web API to use Bearer Tokens のセクションに詳しく書いてありますので、そちらを見てください。

Tags: ,

Web API

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

by WebSurfer 12. December 2018 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(...); の引数によります。上の記事の例では第一引数が文字列(ファイルの物理パス)なので 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

About this blog

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

Calendar

<<  December 2019  >>
MoTuWeThFrSaSu
2526272829301
2345678
9101112131415
16171819202122
23242526272829
303112345

View posts in large calendar