|
.NETエンタープライズ
Webアプリケーション開発技術大全
Webアプリケーションの状態管理
マイクロソフト コンサルティング本部 赤間 信幸
2004/05/20 |
|
2 ViewStateオブジェクト
最も代表的なユーザ状態の管理手法としては、ViewStateとSessionオブジェクトがある。これらを利用する際のコーディング方法は、リスト1のようにいずれもほとんど同じである※1。
※1 本書では、ViewStateやSessionオブジェクトに関する基本的な解説は行わない。ViewStateとは何か、またそれがポストバック処理の中でどのように振舞っているのかに関しては、本シリーズ第2巻「ASP.NET基礎編」のポストバックの解説を参照のこと。 |
// 格納
Session["MyAuthorsDataSet"] = authorsDataSet;
ViewState["MyAuthorsDataSet"] = authorsDataSet;
// 取り出し
authorsDataSet = (AuthorsDataSet)Session["MyAuthorsDataSet"];
authersDataSet = (AuthorsDataSet)ViewState["MyAuthorsDataSet"];
|
|
' 格納
Session("MyAuthorsDataSet") = authorsDataSet
ViewState("MyAuthorsDataSet") = authorsDataSet
' 取り出し
authorsDataSet = CType(Session("MyAuthorsDataSet"), AuthorsDataSet)
authorsDataSet = CType(ViewState("MyAuthorsDataSet"), AuthorsDataSet)
|
リスト1 ViewStateとSessionオブジェクトを使ったユーザ状態管理のコード例 |
2.1 ViewStateとSessionの使い分け
第2巻「ASP.NET基礎編」で解説したように、ViewStateとSessionオブジェクトはデータの保存場所が異なる。ViewStateはクライアントブラウザ内の隠しタグの中にデータを埋め込んでおく保存方法であり、Sessionはサーバ側でデータを保存しておくものである(図2、表2)。
|
図2 ViewStateとSessionオブジェクトの保存場所 |
|
データ保存場所 |
画面遷移時 |
ポストバック時 |
セキュリティ(デフォルト) |
ViewState |
クライアントに返される隠しタグの内部 |
× |
○ |
改ざん防止のみ、解読可能 |
Session |
サーバ上のメモリ(あるいはステートサーバ・DB) |
○ |
○ |
安全 |
|
表2 ViewStateとSessionオブジェクトの特徴 |
ViewStateによるデータ保存はポストバックでしか利用できない。また、クライアントに送信されるためセキュリティ上の各種のリスクが常に付きまとう。このため、ViewStateとSessionは基本的に以下のように使い分ける。
・ViewStateへのユーザ状態の保存 |
|
・ その画面内でしか利用されない一時的な入力仕掛りデータを入れる。 |
|
・ 漏洩(解読)やデータ再送(リプレイ攻撃)されると困るようなデータは含めない(詳細は後述)。 |
|
・ 一時的にダーティになることがあってもよい。 |
|
・Sessionオブジェクトへのユーザ状態の保存 |
|
・ ショッピングカート情報や、一連のウィザード画面での入力仕掛りデータなど、画面間で引き継ぐデータを入れる。 |
|
・ 各画面では入力チェック(フォーマットチェックなど)を完了したデータのみを入れ、常にクリーンに保つように設計するとよい。 |
|
・ 当該画面内でしか利用しないデータであっても、(クレジットカード番号のように)セキュリティを要求されるデータである場合には、Sessionオブジェクトに入れて保存する。 |
例えば、図3のようなeコマースWebサイトにおける、ViewStateとSessionの基本的な使い分けを考えてみよう。
|
図3 ViewStateとSessionの基本的な使い分け |
このような場合における、設計上のキーポイントは以下の通りである。
・ビジネスコンポーネントのメソッドは、1回の呼び出しだけでビジネス処理が完遂できるように設計する。 |
|
・ 個々の画面で入力された購入商品や配送先データ、支払い方法などを、個々の画面から個別にビジネスコンポーネントに渡すような設計をすべきではない。なぜなら、購入商品、配送先、支払い方法の入力が別ページに分かれているのは、使いやすいWeb画面を実現するというUI要件によるものであるためである。例えばWindowsアプリケーションでは、単一のフォームからこれらのデータが一括入力されるかもしれない。 |
|
・ このように考えると、ビジネスコンポーネントは一連の画面で入力された注文データを一括して受け取り、一括してビジネス処理(商品発注処理)を進められるように設計すべきである。 |
|
・Sessionオブジェクトには、入力仕掛データを保存する。 |
|
・ ビジネスコンポーネントに投入する注文データは、複数の画面の処理によって組み立てられていく。その途中のデータを、Sessionオブジェクトに入れて管理する。 |
|
・ 各画面で入力されたデータのチェック(フォーマットチェックや型チェックなど)を済ませた上で、Sessionオブジェクトに入れるようにすることが望ましい。 |
|
・ ここでのSessionオブジェクトの主目的は、複数の画面間でのデータの受け渡しである。ある特定の画面内のみで使われる一時的なデータは、(セキュリティを要求されるものでない限りは)Sessionオブジェクトに入れない方がよい。 |
|
・ViewStateオブジェクトには、各画面内のみで使われる一時的なデータや状態を保存する。 |
|
・ 各入力画面には、画面間で引き継ぐべきショッピングカート的なデータと、当該画面内でのみ使われる一時的なデータがある。 |
|
・ 後者をViewStateに入れて管理することにより、Sessionデータとの役割分担を明確化することができる。 |
以上をまとめれば、画面間で引き継ぐクリーンなデータはSessionオブジェクトに格納し、画面内でのみ利用するデータはViewStateオブジェクトに格納するのが基本、ということになる。ただし、ViewStateオブジェクトに関してはクライアントにデータが保存されるためセキュリティ上のリスクがある。よって、セキュリティを要求されるデータに関しては例外的にSessionオブジェクトに保存するようにするとよい。
以上が最も基本的なViewStateオブジェクトとSessionオブジェクトの使い分けである。
引き続き、ViewStateオブジェクトを利用する際に注意すべき事項に関して、項目別に整理する。主な注意点は以下の通りである。
- ViewStateに格納可能なデータとサイズ
- ViewStateデータサイズの最適化
- Webファームを利用する場合の暗号化鍵の設定
- ViewStateのセキュリティ(内部形式、ハッシュ、暗号化、1クリック攻撃)
2.2 ViewStateに格納可能なデータとサイズ
ViewStateに格納可能なデータは、シリアル化が可能なオブジェクトに限られる。具体的には以下のようなデータ型が格納できる。これら以外のデータ型に関しては、製品ドキュメントを参照し、シリアル化の可否を判断する必要がある。
- 基本データ型(string, int, long, byte, char, decimal, double, float, boolなど)およびその静的配列
- DataSetおよび型付きDataSet
ViewStateに格納したデータは、シリアル化と文字列化が行われ、Hidden INPUT隠しタグを利用してブラウザへと送信される。このことは、ViewStateにデータを格納すると、ネットワーク帯域を消費することを意味する。よって、巨大なデータをViewStateに格納することは避けなければならない。
ネットワーク環境に依存するため、ViewStateの最大サイズを一意に定義することはできないが、特にWAN環境など細い帯域回線を含んだシステムでは、そのサイズに十分な注意を払う必要がある。例えば、64Kbpsのダイヤルアップユーザがいるような環境では、(Webコントロールから出力されるものも合わせた)ViewState全体のサイズを〜1KB程度に抑える必要がある。
2.3 ViewStateデータサイズの最適化
開発環境ではWebアプリケーションをローカルマシン内で動作させてテストするため、ViewStateサイズが巨大になっていることに気づかないことが多い。このため、特に細い帯域の回線を含むシステムを開発する場合には、開発中に必ずViewStateのデータサイズをトラッキングしなければならない。
ViewState全体のサイズは、実際にブラウザに表示された画面のソースコードを参照して確認してもよいが、やや手間がかかる。リスト2のようなコードを利用して、Webブラウザからサーバに送信されてきたデータのサイズを確認すると簡単である※2。
※2 この方法は、ポストバックにしか利用できないことに注意。また、サイズチェックはglobal.asax内で共通的に行うことも可能である。具体的な方法については、後述のSessionデータサイズの確認方法の項を参照のこと。 |
int viewStateSize = Request.Form["__VIEWSTATE"].Length; |
Dim viewStateSize As Integer = Request.Form("__VIEWSTATE").Length |
|
リスト2 サーバに送信されてきたデータのサイズを確認するコード例 |
なお、ViewStateのデータサイズは開発者が格納するDataSetなどのサイズによっても大きく変化するが、場合によっては、Webコントロールから出力されるViewStateのサイズもかなり大きくなることを知っておく必要がある。特に、コレクションデータバインドが可能な以下のようなWebコントロールが出力するViewStateは巨大になりがちである※3。
※3 逆に言えば、TextBoxコントロールなどのWebコントロールが出力するViewStateは極めて小さい。よって、ViewStateのサイズを小さくしようと考える場合には、コレクションデータバインドが可能なWebコントロールから順番にチェックしていくとよい。 |
- DataGrid
- DataList
- Repeater
- CheckBoxList
- RadioButtonList
- DropDownList
- ListBox
もともとViewStateとは、ポストバックにまたがったWebコントロールの表示状態維持とイベント制御のためのメカニズムである。このため、ポストバックを利用しないようなページ、あるいは表示状態を維持する必要のないページの場合にはViewStateをoffにすることができる※4。
※4 Webコントロールの持つEnableViewStateプロパティをfalseにすると、WebコントロールごとにViewStateの出力をoffにすることができる。より積極的なアプローチとしては、web.config内でデフォルトのViewState出力をoffにすることも可能である。。 |
しかし、ポストバックを利用するページの場合には、事実上ViewStateをoffにできないケースがほとんどである。細い帯域の回線を含むシステムで、ViewStateサイズが大きくなる状況をどうしても避けられない場合には、ViewStateデータをSessionオブジェクトに回避し、サーバサイドでViewStateを保持するという方法が利用できる。具体的には、各ページのコードビハインド(.aspx.csファイルまたは.aspx.vbファイル)にリスト3のコードを追加すると共に、デザインページファイル(.aspxファイル)にダミーコードを追加する。
protected override object LoadPageStateFromPersistenceMedium() {
return Session["_ViewState"];
}
protected override void SavePageStateToPersistenceMedium(object viewState) {
Session["_ViewState"] = viewState;
}
|
Protected Overrides Function LoadPageStateFromPersistenceMedium() As Object
Return Session("_ViewState")
End Function
Protected Overrides Sub SavePageStateToPersistenceMedium(ByVal viewState As Object)
Session("_ViewState") = viewState
End Sub
|
……(前略)……
<HTML><BODY>
<form id="Form1" method="post" runat="server">
<asp:TextBox id="TextBox1" runat="server"></asp:TextBox>
<asp:Label id="Label1" runat="server">Label</asp:Label>
<asp:Button id="Button1" runat="server" Text="Button"></asp:Button>
<input type=hidden name=__VIEWSTATE>
※この行はASP.NET 1.0の場合でのみ必要
HTML コードビューを開いて追加する
</form>
</BODY></HTML> |
|
リスト3 各ページに追加するコード |
このようにすると、ViewStateデータはWebサーバ側でSessionデータとして保存されるようになり、ブラウザに返される出力コード中にはViewStateデータが含まれなくなる(リスト4)。
ただし、この方法はむやみに使うべきではない。そもそも、ViewStateデータをクライアント側に送信する仕組みになっているのは、Webサーバ側でのメモリリソース消費を最小限に押さえ、Webサーバ1台あたりで捌けるクライアント数を増やすため(Webシステムのスケーラビリティを確保するため)である。ViewStateデータをサーバ側で保持すれば、その分スケーラビリティを損なうことになる。よって、前述した条件、つまり細い帯域の回線を含むシステムで、ViewStateサイズが大きくなる状況をどうしても避けられない場合に限って、この方法を利用することが望ましい。
<HTML><BODY>
<form name="Form1" method="post" action="WebForm1.aspx" id="Form1">
<input name="TextBox1" type="text" id="TextBox1" />
<span id="Label1">Label</span>
<input type="submit" name="Button1" value="Button" id="Button1" />
<input type="hidden" name="__VIEWSTATE">
</form>
</BODY></HTML> |
|
リスト4 出力コードの内容 |