japan.internet.comThe Internet & IT Network
RSS
  • ニュース
  • コラム
  • リサーチ
  • ヘッドライン
  • 特集
  • ブログ
  • プレスリリース
  • 専門チャンネル
  • イベント
  • ランキング
  • ニュースメール
2008年9月6日
文字サイズ文字サイズ小文字サイズ中文字サイズ大
デベロッパー2007年7月3日 10:00

ASP.NET 2.0でプロパティ永続化コントロールを作成する

海外海外internet.com発の記事
  • このエントリーを含むはてなブックマーク
  • この記事をクリップ!
  • Buzzurlにブックマーク
  • Yahoo!ブックマークに登録
  • newsing it!

はじめに

 ASP.NETでは、いくつかのページレベルの状態維持メカニズムが、ViewStateと新しいControlStateによって実現されています。

 これらのメカニズムは有効に機能しますが、どちらもアプリケーション開発者にとって確定的でないという制限があります。ViewStateはオフにすることが可能ですし、非常に「かさばる」メカニズムのため扱いきれない場合があります。また、ControlStateはコントロールの実装内からしか設定できません。そこで本稿では、より柔軟な状態メカニズムを実現するための新しい手段として、フィールド値の永続化と復元を自動的に行うことのできるプロパティ永続化コントロール(PreservePropertyControl)をViewStateなしで作成してみたいと思います。

 ASP.NET 1.xにはViewStateが用意されており、ASP.NET 2.0ではControlStateが追加されています。どちらのメカニズムも、状態データを__VIEWSTATEという隠しフォーム変数としてページに埋め込むことにより、1つのページのポストバック間でページ固有の状態を維持できるようにします。これらは特定の状況では有効ですが、どちらも大きな制限があるため、状況によっては不適切で、必要以上に使いにくくなるように思います。

 コントロールやページのプロパティ値を宣言的に格納し、ページのポストバック時に値が自動的に復元される組み込みのメカニズムがあれば便利ではないでしょうか?

 たしかにViewStateもそれに近い働きをしますが、ViewStateは宣言的ではなく、制御も容易ではありません。私はできるだけViewStateをオフにするようにしていますが、そうするとViewStateで値を追跡するという機能をまったく利用できなくなります。コントロールの状態は追跡不能になり、アプリケーションコードで生成した値をViewStateコレクションに格納することもできなくなります。一方、ViewStateをオンにすると、変更された非ポストバックの値を持つ(かつViewStateが有効になっている)すべてのコントロールの変更された値が一つ残らず収集されるようになります。

 つまり、ViewStateはオール・オア・ナッシングの手法なのですが、実際のプログラミング現場では、ページの1つか2つの値だけを永続化したい場合がほとんどです。そこでこのミスマッチに悩まされることになります。

 私は数日前に、複雑なデータグリッドを処理していて、まさにそのような状況に遭遇しました。このデータグリッドはViewStateをオフにしてセットアップされています。さらに、各行からポストバックを発生させる「削除」や「状態更新」などさまざまなアクションがあります。また、ページングや並べ替えの機能もあります。ViewStateを使わずにこのデータグリッドのCurrentPageIndexを正しく追跡しようとすると、実に面倒なことになってしまいます。

 このような状況で、もし次のようなことができたら便利だとは思いませんか?

this.gdGrid.PreserveProperty("CurrentPageIndex")

 もしこれが可能になれば、ViewStateを使う必要はなくなります。しかも、データグリッドのすべてのデータを永続化するのではなく、特定の値を選択して永続化できます。他のコントロールのプロパティ(例えばボタンのForeColorなど)も、同じ方法で永続化できるようになります。

this.btnSubmit.PreserveProperty("ForeColor")

 この仕組みはViewStateを有効にしなくとも機能し、Page_Load()や、要求に応じて実行される他のコードの先頭に適切なコードを貼り付けるだけで利用可能になります。最初に必要なコードさえ記述しておけば、ASP.NETが要求の終わりに値を自動的に書き出し、ポストバックでページが再び読み込まれるときに値を復元してくれます。これこそ使いやすくて柔軟性のあるページレベルの状態メカニズムです。

 これと同じ仕組みをコントロールで使用することもできます。そうすると、ASP.NET 2.0でControlStateが行うのと同様の方法で、コントロールの状態をこの同じ状態バッグに確定的に永続化することができます(ControlStateは ASP.NET 2.0の新機能であり、詳しくは後で説明します)。

 ただし、この仕組みの大前提となっているのは、ControlクラスにPreservePropertyメソッドを追加して、すべてのコントロールでこのメソッドを利用できるようにすることです。しかし、Controlクラスに変更を加えるという解決策はMicrosoftのみに許された選択肢です。一般の開発者がControlを拡張し、拡張した方のControlクラスを他のストックコントロールに継承させることはできません。

 我々にできる次善の対応策は、これと同じ機能を実現する外部コントロールを作成し、ページ上にドロップしたりコントロールに追加したりできるようにすることです。本稿では、このようなエクステンダコントロールを作成する方法を紹介します。

PreservePropertyControlの概要

 PreservePropertyControlは、ASP.NET 2.0カスタムサーバコントロールです。本稿のダウンロードサンプルにはASP.NET 1.1版のソースも含まれていますが、これは本コントロールを1.1に移植したPeter Brombergから提供されたものです。私は永続プロパティにジェネリックを利用したいと考えたので、主にASP.NET 2.0を使いました。.NET 2.0では、ジェネリックを使って厳密に型指定されたコレクションを容易に作成できます。さらに、後からデザイナを使い、デフォルトのコレクションエディタを使用してこれらのコレクションを編集することができます。

 このプロパティ永続化コントロールでは、デフォルトのストレージメカニズムを実現するために、2.0のもう1つの機能であるControlStateを利用しています。ControlStateを使うと、内部構造を簡単に永続化でき、その構造をページに埋め込むためのエンコードについて気にせずに済みます。

 今回紹介するPreservePropertyControlは、ページ上で宣言的に定義できる、デザイナサポートを備えたサーバコントロールです。このコントロールは例えば次のように宣言できます。

<ww:PreservePropertyControl ID="Persister" runat="server">
  <PreservedProperties>
    <ww:PreservedProperty
        ID="PreservedProperty1" runat="server"
        ControlId="btnSubmit"
        Property="ForeColor" />
    <ww:PreservedProperty
        ID="PreservedProperty2" runat="server"
        ControlId="__Page"
        Property="CustomerPk" />
  </PreservedProperties>
</ww:PreservePropertyControl>

 この宣言を行うには、ASP.NETページ内にスクリプトとしてマークアップするか、Visual Studioでデザイナおよびデフォルトのコレクションエディタで、永続化したいプロパティを入力します。値を永続化するには、コントロールのUniqueIDと、永続化するプロパティまたはフィールドの名前を指定します。コントロールの値だけでなく、Pageオブジェクトの値も永続化できます(上記のControlId="__Page"の例を参照)。

 もちろん、同様の宣言をコーディングで行うこともできます。

protected PreservePropertyControl Persister=null;
protected void Page_Load(object sender, EventArgs e)
{
  this.Persister=new PreservePropertyControl();
  this.Persister.ID = "Persister";
  this.Controls.Add(Persister);
  this.Persister.PreserveProperty(this.btnSubmit, "ForeColor");
  this.Persister.PreserveProperty(this, "CustomerPk");
}

 コードを使用するときは、文字列のIDよりも実際のコントロール参照を渡すほうが効率的です。PreservePropertyControlはこのようなコントロール参照をキャッシュし、後からページサイクルでその参照を使って値をストレージコンテナに書き込みます。

 このメカニズムによって柔軟性が大きく向上します。コントロールインスタンスを通じて参照できるものであれば、基本的には何でもこの状態コンテナに格納できるようになります。Pageオブジェクトもコントロールなので、永続化したいものすべてにProtectedプロパティをアタッチできます。例えば、ページにPk参照を追加して、ViewStateや隠し変数を使わずに現在の編集コンテキストを追跡できるようにするのにとても便利です。

 オブジェクト全部を格納することさえできます。ViewStateと同様に、PreservePropertyControlにはすべてのシリアル化可能オブジェクトを格納できます。この手法のメリットは、いったんPreserveProperty()呼び出しをセットアップすると、後はプロパティを参照するだけで済むことです。従って、上に示すようにCustomerPkを永続化する場合は、ページのCustomerPkプロパティを参照するだけでよく、状態メカニズムに明示的にアクセスしなくても常に正しい値が得られます。this.CustomerPkと指定するだけで、値が最新の状態になります。

 状態コンテナとやり取りする必要がなくなるため、この手法はViewStateよりも簡単です。状態バッグにアクセスしたり、NULL値を再確認したりする必要はありません。そのような処理はコントロールが管理してくれます。

 コントロール開発者はPreservePropertyControlを内部で使い、PreservePropertyControlのプライベートコピーを使用して独自のプロパティをマップすることもできます。

public class CustomControl : Control
{
  PreservePropertyControl Persister = null;
  protected string value = null;
  protected override void OnLoad(EventArgs e)
  {
    this.Persister = new PreservePropertyControl();
    this.Persister.ID = "__" + this.ID;
    this.Persister.StorageMode =
      PropertyStorageModes.HiddenVariable;
    this.Persister.PreserveProperty(this,"value");
    this.Controls.Add(this.Persister);
    base.OnLoad(e);
  }
}

 いったんこれをセットアップすれば、コントロール開発者は永続化する値をViewState["value"]やカスタムコンテナなどの特別なストアに保存する必要がなくなり、ViewStateを一切アクティブにせずに、プロパティを通常どおり参照するだけで済むようになります。

 本稿のサンプルコードをダウンロードして、実際のPreservePropertyControlの動作と小さなサンプルページを試すことができます。zipファイルには、2.0と1.1の両方のバージョンが含まれています。

PreservePropertyControlの仕組み

 PreservePropertyControlの実装はきわめて単純です。PreservePropertyControlでは、永続化するコントロールのControlID(または、利用できる場合はインスタンス)とプロパティの名前を格納するためのPreservedPropertiesコレクションを用意します。コントロールのPreserveProperty()メソッドを呼び出すか、ページ上でコントロールを宣言的に定義すると、適切なControlIdとプロパティ名がコレクションに格納されます。このコレクションはジェネリックリスト(Generic List)です。今回のような目的にはジェネリックが威力を発揮します。子コントロール用のカスタムコレクションを作成せずに済むからです。

 コレクションをデザイン可能なコレクションとして機能させるには、Controlクラスでコレクションプロパティと共にいくつかのカスタム属性を使う必要があります。また、宣言的スクリプト定義を通じて子コントロールの値をPreservedPropertyオブジェクトとして追加できるようにするために、AddParsedSubObjectを実装する(または、コレクションが1つしかない場合はDefaultProperty属性を使用する)必要もあります。リスト1に、このクラスとコレクションの定義を示します。

リスト1
[ParseChildren(true)]
[PersistChildren(false)]
[DefaultProperty("PreservedProperties")]
public class PreservePropertyControl : Control
{
  ///// <summary>
  ///// Collection of all the preserved properties
  ///// </summary>
  [DesignerSerializationVisibility(
     DesignerSerializationVisibility.Visible)]
  [PersistenceMode(PersistenceMode.InnerProperty)]
  public List<PreservedProperty> PreservedProperties
  {
    get
    {
      return _PreservedProperties;
    }
  }
  List<PreservedProperty> _PreservedProperties =
      new List<PreservedProperty>();

  /// <summary>
  /// Required to add PreservedProperty Collection
  /// </summary>
  protected override void AddParsedSubObject(object obj)
  {
    if (obj is PreservedProperty)
      this.PreservedProperties.Add(obj as PreservedProperty);
  }

  /// <summary>
  /// Adds a control to the collection. At this point only the
  /// control and property are stored.
  /// </summary>
  public bool PreserveProperty(Control WebControl, string Property)
  {
    PreservedProperty pp = new PreservedProperty();
    pp.ControlId = WebControl.UniqueID;
    pp.ControlInstance = WebControl;
    pp.Property = Property;
    this.PreservedProperties.Add(pp);
    return true;
  }

  /// <summary>
  /// Adds a control to the collection. At this point only the
  /// control and property are stored.
  /// </summary>
  public bool PreserveProperty(string ControlId,string Property)
  {
    Control ctl = this.Page.FindControl(ControlId);
    if (ctl == null)
      throw new ApplicationException("Can’t persist control:" +
            ControlId + "." + Property);
      return this.PreserveProperty(ctl, Property);
  }

   ...more implementation code here
}

 このコードでは、厳密に型指定された単純なListコレクションでPreservedPropertiesの格納を処理しています。PreservePropertyControlはカスタムクラスを使用して、コントロールのID、利用できる場合はインスタンス参照、およびプロパティ名を保持します。PreservedPropertiesコレクションは、プロパティの識別情報を一時的に保持するコンテナです。値が実際に格納されるのは、後の要求サイクルの話になります。

永続データのエンコードとデコード

 エンコードおよびデコード用の実際のフックはStorageModeによって異なり、例えばControlState、HiddenVariable、SessionVariable、CachePerPageという選択肢があります。ControlState以外のモードでは、エンコードとデコードを行うために明示的なフックを起動する必要があります。

 これらのモードでは、PreservePropertyControlのOnInit()およびOnPreRender()の中でフックの呼び出しを行います。OnInit()はページサイクルのごく初期の時点で呼び出されるので、これによって他の状態取得よりも先に、保存済みの値が取得されます。具体的には、ViewStateによって値が割り当てられる前、またはポストバック値が割り当てられる前に、プロパティ値が設定されます。その結果、永続化されたプロパティは値割り当ての優先度が最も低くなり、意図したとおりの動作になります。

 値を格納するときは、PreservePropertyControl.OnPreRender()のAs部分で行われます。

ASP.NET 2.0のControlStateによる永続化

 ControlStateはASP.NET 2.0の新機能です。これはコントロール内部の状態を実装する機能であり、ページのポストバック間で重要なデータを格納するために使われます。ストレージにはViewStateを使用しますが、ストックViewStateと異なり、EnableViewStateの設定に関係なく常に値を書き出します。

 ControlStateは、PageクラスのSaveControlState()メソッドとLoadControlState()メソッドを実装することで簡単に操作できます。これらのメソッドは、ASP.NETが内部的に永続化しているオブジェクトの値を保存または取得します。対象となるオブジェクトは、単純型であるか、シリアル化が可能であるか、TypeConverterを実装している必要があります。ViewStateの場合と同様に、規則は.NETのLosFormatterクラス(ASP.NETがViewState文字列のエンコードに使用する最適化されたシリアライザ)を通じて決定されます。ControlStateはSystem.Web.UI名前空間にあります。

 ControlStateを有効にするには、ControlStateを使用することを次のような方法でページに対して宣言します。

this.Page.RegisterRequiresControlState(this);

 通常はこれをコントロールのOnInit()メソッド内で呼び出します。

 次に、SaveControlState()をオーバーライドして、状態を含んでいるオブジェクトを返します。永続化する値が複数ある場合、この目的に使用される代表的なオブジェクトは、永続化したすべての値を含んでいるハッシュテーブルです。ページのポストバック時には、LoadControlState()を呼び出して、SaveControlState()で保存したオブジェクトを実質的に復元するオブジェクパラメータを引き渡します。

 これはすべてのエンコードの詳細を処理する非常に簡単なメカニズムです。必要なのはオブジェクトを返すことだけです。リスト2に、ControlStateを管理するためのSaveControlState()メソッドとLoadControlState()メソッドを示します。

リスト2
/// <summary>
/// Internal persistance object used to serialize
/// into the state store. Hashtable is Serializable
/// and can be serialized by the LosFormatter
/// </summary>
protected Hashtable SerialzedProperties = new Hashtable();
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>
  /// Saves the preserved Properties into a Hashtabe where the key is
  /// a string containing the ControlID and Property name
  /// </summary>
  protected override object SaveControlState()
  {
    foreach (PreservedProperty Property in this.PreservedProperties)
    {
      // *** Try to get a control instance
      Control Ctl = Property.ControlInstance;
      if (Ctl == null)
      {
        // *** Nope - user stored a string or declarative
        Ctl = this.Page.FindControl(Property.ControlId);
        if (Ctl == null)
          continue;
      }
      string Key = Ctl.UniqueID + CTLID_PROPERTY_SEPERATOR +
         Property.Property;
      // *** If the property was already added skip over it
      // *** duplicates are always the same
      if (this.SerialzedProperties.Contains(Key))
         continue;
      // *** Try to retrieve the property
      object Value = null;
      try
      {
        // *** Use Reflection to get the value out
        // *** Note: InvokeMember is easier here since
        //           we support both fields and properties
        Value = Ctl.GetType().InvokeMember(Property.Property,
          BindingFlags.GetField |
          BindingFlags.GetProperty |
          BindingFlags.Instance |
          BindingFlags.Public |
          BindingFlags.NonPublic |
          BindingFlags.IgnoreCase,
          null, Ctl, null);
      }
      catch
      {
        throw new ApplicationException(
           "PreserveProperty() couldn’t read property " +
            Property.ControlId + " " + Property.Property);
      }
      // *** Store into our hashtable to persist later
      this.SerialzedProperties.Add(Key, Value);
   }
   // *** store the hashtable in control state (or return it
   return this.SerialzedProperties;
}

/// <summary>
/// Overridden to store a HashTable of preserved properties.
/// Key: CtlID + "|" + Property
/// Value: Value of the control
/// </summary>
protected override void LoadControlState(object savedState)
{
   Hashtable Properties = (Hashtable)savedState;
   IDictionaryEnumerator Enum = Properties.GetEnumerator();
   while (Enum.MoveNext())
   {
      string Key = (string)Enum.Key;
      string[] Tokens = Key.Split(CTLID_PROPERTY_SEPERATOR);
      string ControlId = Tokens[0];
      string Property = Tokens[1];
      Control Ctl = this.Page.FindControl(ControlId);
      if (Ctl == null)
         continue;
         Ctl.GetType().InvokeMember(Property,
            BindingFlags.SetField |
            BindingFlags.SetProperty |
            BindingFlags.Instance |
            BindingFlags.Public | BindingFlags.NonPublic |
            BindingFlags.IgnoreCase,
            null, Ctl, new object[1] { Enum.Value });
   }
}

 SaveControlState()はPreservedPropertiesコレクションを調べて、各コントロールインスタンスを見つけることから始めます。コードを使ってプロパティを追加した場合はおそらくコントロールのインスタンス参照を渡しているはずですが、宣言的スクリプトを使用した場合は文字列参照が格納されるので、FindControl()を使って実際にコントロールを見つける必要があります。

 コントロール参照が利用できるようになると、プロパティの値がReflection経由で取得されます。フィールドとプロパティ、およびパブリックメンバとパブリックでないメンバを取得するフラグに注意してください。また、Pageオブジェクトのプライベートメンバは保持できないことにも注意してください。

 次は、一意のControlIDとプロパティ名を連結したキーを使ってハッシュテーブルに値を追加します。ハッシュテーブルはキーと値のペアを格納するのに最適な軽量オブジェクトです。その後、SaveControlState()メソッドはこの永続化用のハッシュテーブルを返します。

 ポストバック時のデータの読み込みでは、これと逆の処理が行われます。LoadControlState()はハッシュテーブルを入力として受け取り、コレクションを調べて各キーと値を取得します。LoadControlState()のコードがキーを元のControlIDとプロパティ名のコンポーネントに分割し、FindControl()を使用してインスタンスを取得した後、Reflectionでコントロールの値を設定します。

 これは非常に単純明快な処理で、ControlStateを使うとほとんどコードを必要とせずにこのソリューションを実装できます。

その他の永続化メカニズム

 ControlStateは有効に機能し、おそらく永続データを格納するのに最も信頼の置ける方法ですが、ASP.NET 1.1を使用している場合や、ViewStateによる永続化を(たとえ恒久的でも)好まない場合は、別のストレージメカニズムを使用することもできます。PreservePropertyControlは、次の4つの永続化モードをサポートしています。

  • ControlState
  • HiddenVariable
  • SessionVariable
  • CachePerPage

 ControlState以外のモードでは、ページパイプラインへのカスタムフックと、データをシリアル化するための多少の追加処理が必要です。これの基になるコントロールを作成したときに、私は効率が悪くてかなり多くのコードを必要とする独自のシリアル化メカニズムを使っていました。私のブログに寄せられたいくつかの提案の中で、誰かがLosFormatterを使うことを提案してくれました。LosFormatterはSystem.Web.UI名前空間にある、あまり知られていない文字列シリアライザで、オブジェクトのシリアル化に利用できます。

 このシリアライザで使われるフォーマットは、完全なバイナリフォーマッタよりも軽量です。一般的な型をより効率的にエンコードするうえに、多くの場合、コストのかかる完全な.NETシリアル化メカニズムを迂回するからです。リスト3に、PreservePropertyControlのControlState以外のモードのストレージメカニズムに関するコードを示します。

リスト3
/// <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コードでも、無駄を省いて環境保全に努めましょう!

 本稿に関するご意見、ご質問、ご提案などがあれば、こちらに投稿してください。

著者紹介

Rick Strahl(Rick Strahl)
ハワイのマウイ島にあるWest Wind Technologies社の社長。同社はWebおよび分散アプリケーションの開発とツールを専門にしており、Windowsサーバー製品、. NET、Visual Studio、およびVisual FoxProに主軸を置いている。RickはWest Wind Web Connection、West Wind Web Store、およびWest Wind HTML Help Builderの作成者である。C# MVPで、雑誌や書籍に頻繁に寄稿し、国際的な開発者会議で頻繁に講演を行っている。『CoDe Magazine』誌の共同発行者でもある。詳細については、彼のWebサイト(www.west-wind.com)を参照。
関連テーマ
最新トップニュース
データメーション
【データメーション】
OSについて気に入らないこと(9月5日)
ベンチャー専門家の目利きブログ「なぜこの企業は伸びるのか?」
【ベンチャー専門家の目利きブログ「なぜこの企業は伸びるのか?」】
「導入期〜成長期へ!一歩一歩と前進を目指す『Annoii(アノイ)』」/maka hou,Inc.(9月5日)
最新テクノロジーの意外な処方箋
【最新テクノロジーの意外な処方箋】
グリッドコンピューティング技術でETに遭遇(9月5日)
Graphic Design Forum
【Graphic Design Forum】
古い Emigre を探して (9月3日)
エンジニアの独り言
【エンジニアの独り言】
データをローカルに保存するWebアプリケーション(8月22日)
デスマーチからの脱却
【デスマーチからの脱却】
30min. iPhoneアプリリリース(8月18日)
最新ハイテク講座
最新ハイテク講座
なぜ勝った? 世界No.1シェアをつかんだ“Windows”(9月5日)
developer.com
developer.com
デザインパターンの使い方: Composite(9月5日)
最新アフィリエイト事例にみる成功の法則
最新アフィリエイト事例にみる成功の法則
コンバージョンレートを高めよう!(9月5日)
百式のネットビジネス研究
百式のネットビジネス研究
ガジェット購入時に将来の買取保証プランを提供する「TechForward」(9月5日)
週刊-サイト別アクセス状況データ
週刊-サイト別アクセス状況データ
ビデオリサーチインタラクティブ調査(月間インターネットオーディエンスデータ)(9月4日)
「IT の耳」
「IT の耳」
【書評】『検索にガンガンヒットさせる SEO の教科書』――SEO テクニックで効果的に PR する(9月4日)
検索エンジンマーケティング
検索エンジンマーケティング
果たしてモバイル SEO は必要なのか?(9月4日)
Eメールマーケティング事情
Eメールマーケティング事情
読者が迷惑メールと認識する時…(9月3日)
日本と韓国のインターネットビジネス最新動向調査
日本と韓国のインターネットビジネス最新動向調査
日本と韓国の動画サイト比較1―現状(9月3日)
SNSをビジネスに活用しよう
SNSをビジネスに活用しよう
「しまじろう」に学ぶ企業内コミュニティの活性化のポイント(9月2日)
海外のインターネットコムアメリカ韓国ドイツトルコ
Copyright 2008 Jupitermedia Corporation All Rights Reserved.http://www.internet.com/