WebSurfer's Home

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

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

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

UpdatePanel と ListView

by WebSurfer 2016年11月20日 17:24

先に今回の事例から学んだ教訓を書いておきます。ちょっと今さらながらの感がありますが。

ASP.NET 4 以降では、UpdatePanel を使って非同期ポストバックを行う場合、ポストバックのトリガとするコントロールの ClientIDMode プロパティはデフォルトではなく AutoID にしておくこと。

UpdatePanel に LinkButton を含む ListView を配置した場合、LinkButton をクリックしても非同期ポストバックとならず、同期ポストバックになることからこの問題に気がつきました。(注: GridView の場合は非同期ポストバックがかかります・・・理由後述)

UpdatePanel と ListView

これは LinkButton に限らず、ASP.NET が自動生成するスクリプトの __doPostBack メソッドを利用してポストバックをかけるコントロールすべてに同様です。例えば TextBox の AutoPostBack プロパティを true にしたような場合も非同期ポストバックはかかりません。

そもそもの原因は ASP.NET サイトの記事 ASP.NET 4 Breaking Changes / ClientIDMode Changes にあるように、ASP.NET 4 からは ClientID の命名がデフォルトで Predictable となったからです。(正確に言うと、コントロールの ClientIDMode はデフォルトで Inherit だが、ページの ClientIDMode はデフォルトで Predictable なので、それを継承して結果的に Predictable になるということです)

ScriptManager が生成するスクリプトは前の方法、すなわち AutoID モードでの ClientID を期待しているので、デフォルトの Predictable では非同期ポストバックのトリガとなるコントロールとしては認識されません。

具体的にどういう話かというと、以下の通りです。

上の画像のように ListView 内に LinkButton を配置すると、ASP.NET がそれを html ソースにレンダリングする時、ClientIDMode が Predictable の場合と AutoID の場合とで id は以下のように異なります。上が Predictable、下が AutoID の場合です。

<a id="ListView1_LinkButton1_0" 
 href="javascript:__doPostBack('ListView1$ctrl0$LinkButton1','')">
 LinkButton
</a>

<a id="ListView1_ctrl0_LinkButton1"
 href="javascript:__doPostBack('ListView1$ctrl0$LinkButton1','')">
 LinkButton
</a>

__doPostBack メソッドの第一引数にはコントロールの UniqueID が設定されます。UniqueID の命名規則は、親名前付けコンテナーの ID 値とコントロールの ID 値を '$' で連結するもので、上の例では 'ListView1$ctrl0$LinkButton1' となります。

AutoID の場合、UniqueID の '$' を '_' に置き換えた文字列が id の文字列と同じになるとことに注目してください。Predictable ではそうはなりません。

ScriptManager がページに配置されると、それが生成する JavaScript のコードでポストバックを制御するようになりますが、その時 UniqueID を使ってトリガとなった html 要素を探し、同期 or 非同期どちらのトリガになるかを判定します。

具体的には、ScriptManager が生成する _doPostBack という名前のメソッドの中で、ポストバックのトリガとなったコントロールの UniqueID の '$' を '_' に変換した id で getElementById メソッドを使って要素を探しに行きます。

なので、ClientIDMode がデフォルトでは見つからない、AutoID の場合は見つかるということになります。

上のステップで html 要素が見つかった場合は非同期ポストバックのトリガと判定され、直ちに非同期ポストバックがかかるようになっています。

上のステップで直接当該要素を見つけられなかった場合は、_findNearestElement というメソッドを使って上位の要素を探しに行きます。上の例の html ソースの UniqueID の場合は最終的に ListView1 という名前の id の要素を探しに行きます。

ところが ID="ListView1" という ListView コントロールから ASP.NET が生成する html 要素(table)の id は "ListView1_itemPlaceholderContainer" になり、ListView1 という名前の id の要素は見つからないという結果になります。

直接見つからず、その親も見つからなかった場合は、当該要素は非同期ポストバックのトリガとは見なされず、同期ポストパックがかかるという結果になります。

ただし、ListView に代えて GirdView を使った場合は、その中に配置した LinkButton クリックでも非同期ポストパックがかかります。その理由は、例えばサーバー側での ID を "GridView1" とした場合、html 要素(table)の id は同じく "GridView1" になるので、_findNearestElement メソッドで親が見つかるからです。

しかし、やはり正解は、ListView、GridView に関係なく、UpdatePanel を使って非同期ポストバックを行う場合トリガとするコントロールの ClientIDMode プロパティは AutoID にしておくことだと思います。

Tags: , ,

AJAX

SQL Server 接続プロトコル順序

by WebSurfer 2016年11月16日 23:43

SQL Server の接続プロトコルとして (1) 共有メモリ、(2) TCP/IP、(3) 名前付きパイプが有効になっている場合、どの順序で接続をトライするかという話を書きます。

SQL Server の Protocol Order

元は MSDN Forum の「アプリケーションからSQLServerへの接続プロトコル(名前付きパイプ)について」という表題のスレッドでの話です。

MSDN Forum で回答を書くためググって調べて見つけた記事 SQL Server clients may change protocols when the client computers try to connect to an instance of SQL Server に書いてあったことですが、レジストリの ProtocolOrder で指定されている順番で接続をトライするとのことです。

確かに自分の PC(SQL Server 2005 Developer Edition と SQL Server 2008 Express がインストールされています)のレジストリを見ると、上の画像のような設定がありました。

レジストリの設定は変えてないので、デフォルトで sm tcp np の順、即ち、(1) 共有メモリ ⇒ (2) TCP/IP ⇒ (3) 名前付きパイプの順に接続をトライしていくようです。(実際にその順番で接続をトライしているかは確認していませんが)

MSDN Forum の質問者さんのケースでは「時々名前付きパイプで接続しにいき失敗している」ということでしたが、時々何らかの問題で TCP/IP の接続に失敗した後、最後に名前付きパイプで接続に行って失敗し、「SQL Server への接続を確立しているときに・・・ (provider: 名前付きパイプ プロバイダ・・・」というエラーメッセージになったものと思われます。

Tags:

SQL Server

About this blog

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

Calendar

<<  2017年2月  >>
2930311234
567891011
12131415161718
19202122232425
2627281234
567891011

View posts in large calendar