その他の永続化メカニズム
ControlStateは有効に機能し、おそらく永続データを格納するのに最も信頼の置ける方法ですが、ASP.NET 1.1を使用している場合や、ViewStateによる永続化を(たとえ恒久的でも)好まない場合は、別のストレージメカニズムを使用することもできます。PreservePropertyControlは、次の4つの永続化モードをサポートしています。
- ControlState
- HiddenVariable
- SessionVariable
- CachePerPage
ControlState以外のモードでは、ページパイプラインへのカスタムフックと、データをシリアル化するための多少の追加処理が必要です。これの基になるコントロールを作成したときに、私は効率が悪くてかなり多くのコードを必要とする独自のシリアル化メカニズムを使っていました。私のブログに寄せられたいくつかの提案の中で、誰かがLosFormatterを使うことを提案してくれました。LosFormatterはSystem.Web.UI名前空間にある、あまり知られていない文字列シリアライザで、オブジェクトのシリアル化に利用できます。
このシリアライザで使われるフォーマットは、完全なバイナリフォーマッタよりも軽量です。一般的な型をより効率的にエンコードするうえに、多くの場合、コストのかかる完全な.NETシリアル化メカニズムを迂回するからです。リスト3に、PreservePropertyControlのControlState以外のモードのストレージメカニズムに関するコードを示します。
/// <summary> /// Read in data of preserved properties in OnInit /// </summary> protected override void OnInit(EventArgs e) { base.OnInit(e); if (this.Enabled) { if (this.StorageMode == PropertyStorageModes.ControlState) this.Page.RegisterRequiresControlState(this); else if (this.Page.IsPostBack) this.LoadStateFromLosStorage(); } } /// <summary> /// Write out data for preserved properties in OnPreRender /// </summary> protected override void OnPreRender(EventArgs e) { if (this.Enabled && StorageMode != PropertyStorageModes.ControlState) this.SaveStateToLosStorage(); base.OnPreRender(e); } /// <summary> /// Saves state the specified storage mechanism by /// first serializing to a string with the LosFormatter /// </summary> private void SaveStateToLosStorage() { string Serialized = LosSerializeObject(this.SaveControlState()); if (this.StorageMode == PropertyStorageModes.HiddenVariable) this.Page.ClientScript.RegisterHiddenField( "__" + this.UniqueID, Serialized); else if (this.StorageMode == PropertyStorageModes.SessionVariable) HttpContext.Current.Session["__" + this.UniqueID] = Serialized; else if (this.StorageMode == PropertyStorageModes.CachePerPage) { if (this.PreservePropertyKey == null) this.PreservePropertyKey = Guid.NewGuid().ToString().GetHashCode().ToString("x"); HttpContext.Current.Cache[this.PreservePropertyKey] = Serialized; this.Page.ClientScript.RegisterHiddenField( "__PreservePropertyKey", this.PreservePropertyKey); } } /// <summary> /// Retrieves the serialized data from the Storage medium /// as string using LosFormatter formatting. /// </summary> private void LoadStateFromLosStorage() { string RawBuffer = null; if (this.StorageMode == PropertyStorageModes.HiddenVariable) { RawBuffer = HttpContext.Current.Request.Form["__" + this.UniqueID]; if (RawBuffer == null) return; } else if (this.StorageMode == PropertyStorageModes.SessionVariable) { RawBuffer = HttpContext.Current.Session["__" + this.UniqueID] as string; if (RawBuffer == null) return; } else if (this.StorageMode == PropertyStorageModes.CachePerPage) { this.PreservePropertyKey = HttpContext.Current.Request.Form["__PreservePropertyKey"]; if (this.PreservePropertyKey == null) return; RawBuffer = HttpContext.Current.Cache[this.PreservePropertyKey] as string; } if (RawBuffer == null) return; // *** Retrieve the persisted HashTable and pass to LoadControlState // *** to handle the assignment of property values this.LoadControlState(LosDeserializeObject(RawBuffer)); } private string LosSerializeObject(object obj) { LosFormatter output = new LosFormatter(); StringWriter writer = new StringWriter(); output.Serialize(writer, obj); return writer.ToString(); } private object LosDeserializeObject(string inputString) { LosFormatter input = new LosFormatter(); return input.Deserialize(inputString); }
ControlState以外の永続化モードでは、OnInit()メソッドとOnPreRender()メソッドの両方がフックされます。OnInit()はLoadStateFromLosStorage()を呼び出し、OnPreRender()はSaveStateToLosStorage()を呼び出します。これらのメソッドは、実際のハッシュテーブルの生成と解析を上述のSaveControlState()およびLoadControlState()に委任します。次に、SaveStateToLosStorage()はLosFormatterを呼び出して、このハッシュテーブルの文字列表現を作成します。それぞれのストレージメカニズムは、この文字列を適切なストレージメカニズムで格納します。
HiddenFormVariableでは、データは次のように格納されます。
this.Page.ClientScript.RegisterHiddenField( "__" + this.UniqueID, Serialized);
この場合は、次のようなコードを使ってデータを読み込みます。
RawBuffer = HttpContext.Current.Request.Form[ "__" + this.UniqueID];
取得されたローバッファは、シリアル化解除されてハッシュテーブルに戻り、LoadControlState()に渡されます。このメソッドは値をコントロールに再割り当てします。
セッション変数ストレージは少し異なるため、少々説明が必要です。セッションストレージでは、保持されたプロパティの状態を、すべてのページに再利用されるセッション変数に格納します。従って、すべてのページが同じセッション変数インスタンスを取得し、ユーザーごとに1つの変数があります。
セッションを使うことには大きな利点があります。永続データをSessionオブジェクトに格納すると、データがネットワーク経由で送信されないので、ページのサイズが小さくなります。また、InProcストレージを使用するとシリアル化が発生しないので、セッションストレージはいかなる種類のViewStateエンコードよりもはるかに高速になる傾向があります。
この手法を使うときは注意が必要です。同じブラウザセッションで2つのウィンドウを開いたり、同じセッションが同時にアクティブになるフレームページを使用したりすると、誤った状態が復元されるという問題が発生します。サンプルのデモページでこれを試すことができます。ページを実行し、色を設定し、送信します。
次にCtrl+nキー(またはタブがある場合はCtrl+tキー)を押して、同じブラウザセッションで新しいブラウザウィンドウを作成します。同じページを開き、別の色を選択して[Show]をクリックします。
今度は最初のページに戻り、ポストバックボタンをクリックします。2番目のインスタンスの色が最初のインスタンスに表示されるはずです。これは明らかに誤りです。最初のインスタンスが2番目のインスタンスの永続データを取得してしまっています。
複数のブラウザウィンドウやフレームを実行する必要がない内部アプリケーションでは、これが問題にならないこともありますが、そのような場合でもこのオプションの使用には十分に注意してください。セッション変数に対して一意のページ単位のIDを生成することで、この問題を解決できる場合もありますが、それによってセッション状態が大量の永続メモリでいっぱいになる可能性があります。セッション変数ストレージの使用は、大量の永続データが含まれるページを扱う場合で、ポストバックのたびにネットワーク経由でデータを送信するのを避けたい場合にのみ検討してください。
同様に、キャッシュオブジェクトを使用することもできます。セッションの手法と異なり、キャッシュの手法ではセッションに書き込み、ページごとに新しいGUIDを割り当てます。そのため、すべてのユーザーのすべての新しいページが新しいキャッシュエントリを作成します。
セッションの場合と同様に、トレードオフに注意してください。Cacheオブジェクトを使うと多数のキャッシュエントリが生成されるため、多くのページから成るアクセス数が非常に多いサイトでは、この手法はおそらく現実的ではありません。また、キャッシュは複数のマシン間では保持されないため、Webファーム環境ではキャッシュの手法を利用できません。
それでも、キャッシュやセッションの手法によってページが軽量になり、状態ストレージのパフォーマンスが向上するので、これらのオプションを試してみることをお勧めします。
ControlStateとHiddenFormVariableの手法は、どちらもページ自体にPOSTデータとして格納されるので、こうした問題に見舞われることはありません。この2つでは、ControlStateの方が信頼性に勝ります。ControlStateにはページパイプラインの一部として発生する独自のイベントがあり、他のコントロールとのタイミングの問題が発生しかねない既存のイベントのフックが不要だからです。HiddenFormVariableの実装は、主にASP.NET 1.xをサポートするために提供しています。
無駄を省いて環境保全
最近開発したいくつかのアプリケーションでPreservePropertyControlを使ってみて、これまで状態ストレージを検討すらしなかったような場面でこのコントロールが大いに役立つことが分かりました。永続化する対象を正確に制御できると、永続化された状態のサイズを小さく抑えつつ、永続化された値への真のプロパティアクセスを提供する柔軟なページを簡単に作成できます。私は自分のページでViewStateを全部まとめてオフにする傾向があるため、いくつかのアイテムを宣言的に永続化できるコントロールがあると非常に助かります。全体として、このコントロールはさまざまな形で役に立ちます。
私の場合、最もよくあるシナリオは、グリッドやリストのリストコントロール、ページング操作、および並べ替えの設定によるSelectedValueプロパティなどのリスト状態の設定を管理することです。現在のレコードIDなどのページレベルの状態の格納も非常にうまく機能します。
このコントロールの良い点は、保持されたプロパティを操作するのがはるかに自然になることです。永続化メカニズムを一切気にせずに、プロパティを参照するだけで済むからです。いったんPreservePropertyControlに追加してしまえば、処理は完全に透過的です。基本的に、このコントロールを使うとASP.NETの無差別なViewStateポリシーを根本から変えることができます。何もかも自動的にViewStateに格納する代わりに、明確に格納する必要があるものだけを格納します。これにより、ページ内でViewStateに要求されるサイズを激減させることができます。このコントロールでページのサイズを大幅に削減できないかどうか、各自で試してみてください。
このコントロールが読者の役に立ち、本稿がさまざまなページレベルの状態維持メカニズムを理解する手がかりになることを願っています。実生活でもWebコードでも、無駄を省いて環境保全に努めましょう!
本稿に関するご意見、ご質問、ご提案などがあれば、こちらに投稿してください。
