atsukanrockのブログ

Microsoft系技術を中心にぼちぼち更新します

Repeaterのコントロールツリー復元機能

はじめに

ASP.NETでの開発効率を上げるために、使いこなすことが非常に重要なコントロールのひとつにRepeaterコントロール(以降、単に「Repeater」と呼ぶ)がある。本エントリでは、Repeaterのコントロールツリー復元機能について述べる。
なお、本エントリの内容は、おそらくドキュメント化されておらず、私が独自に調査した結果に基づく。

コントロールツリー復元機能とは?

Repeaterには、ViewStateからのコントロールツリーの復元機能がある。どういうことかというと、RepeaterでViewStateが「本当に」有効になっている場合、Repeaterへのデータバインドを一度行えば、ViewStateが保存される期間の間*1は、再度データバインドを行わなくても、Repeater以下のコントロールツリーが復元されるということだ。

仕組み

Repeaterは、どのようにしてコントロールツリーを復元しているのか、その仕組みを説明する。

まずは

そのためにはまず、ViewStateの仕組みが理解できていなければならない。本ブログのエントリ「ViewStateによるコントロールのプロパティ値復元の仕組み」を参照されたい。

RepeaterがViewStateに保存する値

Repeaterは、データバインドされると、その項目数(Items.Count)をViewStateに保存する。このことによりRepeaterは、一度データバインドされた後は、データバインドされた際のデータ項目数を、再度データバインドすることなく知ることができる。

Itemsプロパティのgetterでの処理

RepeaterのItemsプロパティのgetterでは、上記で保存した項目数を基に、項目の復元処理が行われる。その際に活用されるのが、テンプレート機能だ。

テンプレート機能とは

Repeaterを使用する場合、以下のようなコードを記述すると思う。

<asp:Repeater ID="rpt" runat="server">
    <ItemTemplate>
        <asp:Label ID="lbl" runat="server" Text="<%# Container.ItemIndex %>" />
    </ItemTemplate>
</asp:Repeater>

このItemTemplateタグで囲まれた範囲がテンプレートだ。テンプレートのプロパティを公開しているコントロール(ここではRepeater)では、その内部のコントロールツリーを、いつでも、いくつでも生成することができる。

項目の復元処理

Repeaterは、以下の2つを組み合わせることで、項目を復元することができる。

  • ViewStateに保存しておいた項目数
  • テンプレート機能

ただし、ここまでで復元できるのは、コントロールツリーと、aspxのタグ内で設定されたプロパティ値までであり、プログラムで変更されたプロパティ値が復元できない。

プログラムで変更されたプロパティ値の復元

プログラムで変更されたプロパティ値の復元は、静的コントロールと同様、ViewStateからのプロパティ値復元機能で行われる。Repeaterは、テンプレート機能によって前回のHTTPレスポンス処理時と全く同じコントロールツリーを復元できるので、Repeater内に配置した各コントロールに、その各コントロールのViewStateを渡すことができる(その処理はRepeater独自のものでなく、Controlで定義されている)。そして、Repeater内に配置した各コントロールのプロパティ値が、ViewStateから復元される。
Repeaterが行っているのは、あくまで各コントロールのViewStateを渡すところまでであることに注意する。渡したViewStateがどう処理されるかを、Repeaterは知らない。

復元できる場合とできない場合

基本的には上記のとおり、一度Repeaterにデータバインドしてしまえば、次回以降のポストバックではコントロールツリーとそのプロパティ値までが完全に復元される。しかし以下のような場合、例外的に復元できない。

  • RepeaterのViewStateが有効でない
  • テンプレート内に動的に追加されたコントロール
RepeaterのViewStateが有効でない

上記の仕組みを理解していれば当然であることがわかるが、RepeaterのViewStateが有効でない場合は復元できない。Repeaterが項目数をViewStateに保存することができないからだ。
なお、ここでの「有効でない」は、EnableViewStateでなくIsViewStateEnabledの方の意味であることに注意する。

テンプレート内に動的に追加されたコントロール

テンプレート機能は、動的に追加されたコントロールを復元する仕組みを備えていない。よって、動的に追加されたコントロールの部分は復元できない。
さらに言うと、Templateへの動的コントロール追加時に、コントロールツリーの末尾に追加していれば良いのだが、末尾以外に追加してしまうと復元機能が狂ってしまい、テンプレート内に静的に配置されたコントロールさえ正常に復元されなくなる。ただしこれは、Repeaterに限った話ではない。

Repeater内のコントロールでのイベントとの関係

コントロールツリーの復元機能は、Repeater内に配置したコントロールで発生するイベントにも深く関係する。
ASP.NETのイベント処理の詳細については別途説明したいと思っているが、イベントが正常に発生するためには、前回のHTTPレスポンス処理時とコントロールツリー(正確にはUniqueID)が一致する必要がある。よって、Repeaterにコントロールツリー復元機能がないと、Repeater内に配置したコントロールでイベントが発生しなくなってしまう。このことをこれまでの話と考え合わせると、以下のとおりにしなければ、Repeater内に配置したコントロールでイベントが正常に発生するようにするのが、非常に難しいことがわかる。

  • RepeaterのViewStateが有効にする
  • イベントを発生させたいコントロールは、テンプレート内に静的に配置する(動的コントロールとしない)

ただ、難しいのは確かだが不可能ではない。どうすれば良いのかというと、ページのLoadイベントまでの間に、コントロールツリーを完全に復元すれば良い。イベントを発生させる処理は、ページのLoadイベント完了後に行われるためだ。そこで、上記のとおりにしない場合の代替案を挙げる。

ViewStateが無効なRepeaterでは

前回のHTTPレスポンス処理時にRepeaterにバインドしたデータソースを、SessionなりViewStateなりに保存しておき、ページのLoadイベントまでの間にRepeaterにデータバインドする。ただし、ViewStateに保存しておくくらいであれば、RepeaterのViewStateを有効にした方が良いだろう。これに対し、Sessionに保存すればRepeaterのViewState分の通信量が削減できる。インターネットサイトなど、できるだけ通信量を削減したい場合で、サーバリソースが潤沢なのであれば、この方法が良いかもしれない。

動的コントロール

何とかしてコントロールツリーを完全に復元する。それしかない。「ViewStateが無効なRepeaterでは」で挙げたのと同様、データソースを保存しておけば、それほど難しくはないだろう。

GridViewなどではどうか

本エントリではRepeaterを例にとったが、GridViewなどRepeater以外のデータ バインド Web サーバー コントロールでも概ね同じだと思われる。

*1:ポストバックが連続している間