by WebSurfer
2018年12月20日 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 ジェネリックハンドラに同様な機能を実装することは可能です。
by WebSurfer
2016年12月14日 23:49
ASP.NET Web Forms アプリにおいて、ファイルをチャンク形式エンコーディングしてブラウザにダウンロードするサンプルを備忘録として書いておきます。
チャンク形式エンコーディングとは 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 で以下の画像のように通知バーが表示されます。
上の通知バーの[ファイルを開く(O)]または[保存(S)]をクリックすると通知バーは以下の画像のように変わります。(注:Thread.Sleep(5000) のコメントアウトを外して試してください)
その後、最初に表示された通知バーで[ファイルを開く(O)]をクリックしていた場合はダウンロードが完了すると画像 Sig552T8.jpg がブラウザ上に表示されます。
最初に表示された通知バーで[保存(S)]をクリックしていた場合はダウンロードが完了すると通知バーは以下の画像のように変わります。
[ファイルを開く(O)]または[フォルダーを開く(P)]でダウンロードした画像ファイルを確認できます。