ViewStateによるコントロールのプロパティ値復元の仕組み
はじめに
本エントリではまず、ViewStateの基礎的な内容について説明する。さらに、ViewStateによるコントロールのプロパティ値復元の仕組みについても説明する。
ViewStateというと、「@IT:.NETエンタープライズWebアプリケーション開発技術大全 Webアプリケーションの状態管理」で紹介されているように、「Sessionオブジェクトと同じような、データの保存用オブジェクト」といったレベルの認識がされやすい。この認識は正しい。しかし十分ではない。ViewStateは、それがないとASP.NETはまともに動作しないというほど重要な、ASP.NETの基盤技術のひとつなのだ。
ViewStateの詳細は、「ASP.NET ビューステート」や「サーバー コントロールのカスタム状態管理」で述べられているので省略し、本ブログの他のエントリでの説明のために必要な内容だけを述べる。
ViewStateは、コントロールの状態を保存する
例えばLabelコントロールのTextプロパティに、"hoge"を設定したとする。すると、Textプロパティのsetterでは以下のような処理が行われる。
ViewState["Text"] = "hoge";
ちなみに、プロパティのgetterは以下のように実装される。
String s = ViewState["Text"] as String; return (s == null) ? String.Empty : s;
基本的に、ASP.NET Web サーバー コントロールは、プロパティ値をインスタンスフィールドとしては保持しない。Stringをキー、Objectを値とするディクショナリであるViewStateに、プロパティごとに異なる文字列をキーとして保存する。
ちなみに、ViewStateに保存されるのはプログラムで変更した*1プロパティ値のみである。aspxでコントロールのタグ内に記述したプロパティ値はViewStateには保存されず、コントロールのInitイベント(正確には、その周辺)でASP.NETランタイムによって設定される。
ViewStateは、XMLにシリアライズされる
HTTPレスポンスのHTML生成時、ViewStateはXMLにシリアライズされる。そのXMLがBase64エンコーディングされ、type="hidden"、name="__VIEWSTATE"のinput要素のvalue属性値に設定され、レスポンスHTMLに埋め込まれる。
ブラウザのHTMLソース表示機能を使ってASP.NETページのHTMLソースを確認すると、type="hidden"、name="__VIEWSTATE"のinput要素があるはずだ。それがViewStateのシリアライズ結果だ。
ちなみに、「ViewState Decoder」というツールを使えば、このViewStateのシリアライズ結果をデシリアライズし、ViewState内のオブジェクトのツリー構造や、シリアライズ結果のXMLを確認することができる。
XMLの階層構造は、コントロールツリーに対応する
XMLは、言うまでもなくツリー構造である。そのツリー構造は、コントロールツリーに対応する。
例えば、以下のようにPanelの中にLabelを配置したとする。
<asp:Panel ID="pnlHoge" runat="server"> <asp:Label ID="lblHoge" runat="server" /> </asp:Panel>
すると、コントロールツリーは以下のようになる。
Page + pnlHoge + lblHoge
そして、この階層構造とシリアライズされたViewStateのXMLの階層構造が対応する。以下にXMLのイメージ*2を示す。
<PageのViewState> <PageのViewStateに保存された値> <値1 /> <値2 /> </PageのViewStateに保存された値> <pnlHogeのViewState> <pnlHogeのViewStateに保存された値> <値1 /> <値2 /> </pnlHogeのViewStateに保存された値> <lblHogeのViewState> <lblHogeのViewStateに保存された値> <値1 /> <値2 /> </lblHogeのViewStateに保存された値> </lblHogeのViewState> </pnlHogeのViewState> </PageのViewState>
あるコントロールでEnableViewStateプロパティにfalseを設定すると、コントロールツリーでそのコントロールの子孫階層にある全てのコントロールでViewStateが無効となる理由も、このあたりにある。
ここまでのまとめ
プログラムでのコントロールのプロパティ値変更を行うと、以下の順序で処理されていく。
- プロパティのsetterで、ViewStateに値が保存される
- HTTPレスポンス処理の最終段階(PreRenderイベント発生より後で、Renderメソッド呼び出しより前)でSaveViewStateメソッドが呼び出され、ViewStateがXMLにシリアライズされる
- XMLにシリアライズされたViewStateが、Base64エンコーディングされる
- Base64エンコーディングされた結果の文字列が、type="hidden"、name="__VIEWSTATE"のinput要素のvalue属性値に設定され、HTTPレスポンスのHTMLに埋め込まれる
- クライアントブラウザ(以降、単に「ブラウザ」と呼ぶ)にHTTPレスポンスが返される
ViewStateの復元処理の詳細
上記のようにして、ようやくViewStateがブラウザに送信された。そして、ユーザがブラウザ上でボタンクリックなどの操作を行うと、上記のBase64エンコーディングされた文字列がサーバにポストされ、ViewStateを復元できることになる。
このViewStateの復元処理で特に理解しておかなければならないのは、以下の点である。
コントロールのプロパティ値復元ロジック
以上の内容を踏まえ、コントロールのプロパティ値復元ロジックを説明する。基本的に、以下の順序で処理が行われる。
- HTTPリクエストが到着する
- Initイベントで、aspxでコントロールのタグ内に記述したプロパティ値が設定される
- ViewStateがロードされ(LoadViewStateメソッド)、前回のHTTPレスポンス処理時にプログラムで変更した(データバインドを含む)プロパティ値が設定される
- ユーザ入力値がロードされ(LoadPostDataメソッド)、今回のHTTPリクエストの際にユーザが入力した値が設定される
ちなみに、このInitイベント以降の処理は、基本的にはHTTPリクエスト到着直後に、コントロールツリーの子階層から親階層へ順に発生していく。ただしそれは、aspxでコントロールの配置が決まっている、静的コントロールの場合だけだ。ページの初期化の段階でコントロールの配置が決まっていないコントロールは、動的コントロールと呼ばれる。動的コントロールでは、Initイベントは親階層のコントロールへの追加時(Controls.Addメソッド呼び出し時)に発生する。「動的なコントロールの追加の説明 - zorioの日記」に詳しいので、参照されたい。ユーザ入力を受け取りたいコントロールは、ページのLoadイベントまでにコントロールツリーに追加しなければならないということがわかるはずだ。