SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

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

ViewStateではなくPreservePropertyControlを使った永続化

ダウンロード サンプルソース (62.6 KB)

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を使うとほとんどコードを必要とせずにこのソリューションを実装できます。

次のページ
その他の永続化メカニズム

この記事は参考になりましたか?

japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

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 Bui...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1327 2007/05/22 08:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー