複数のテンプレートに対応する
このままでは、晴れ以外をサポートすることができていません。Templateの機能という点では少し寄り道になりますが、これを「晴れ、雨、曇り」の3つのテンプレートを使うように変更してみましょう。aspxは次のようになります。
<MU:Weather runat="server"> <SunnyTemplate> 晴れ<br /> </SunnyTemplate> <CloudyTemplate> 曇り<br /> </CloudyTemplate> <RainyTemplate> 雨<br /> </RainyTemplate> </MU:Weather>
次に、このテンプレートを受け取るWeatherコントロールのコードを追加します。
private ITemplate _sunnyTemplate; [TemplateContainer(typeof(WeatherDayContainer))] public ITemplate SunnyTemplate { set { _sunnyTemplate = value; } } private ITemplate _cloudyTemplate; [TemplateContainer(typeof(WeatherDayContainer))] public ITemplate CloudyTemplate { set { _cloudyTemplate = value; } } private ITemplate _rainyTemplate; [TemplateContainer(typeof(WeatherDayContainer))] public ITemplate RainyTemplate { set { _rainyTemplate = value; } }
こうするとASP.NETが3つのITemplateをセットしてくれるので、CreateChildControlsでそれらを使い分けるようにします。
protected override void CreateChildControls() { foreach (WeatherDataItem item in _dataSource.Weathers) { WeatherDayContainer container = new WeatherDayContainer(); Controls.Add(container); switch (item.Type) { case WeatherType.Sunny: _sunnyTemplate.InstantiateIn(container); break; case WeatherType.Cloudy: _cloudyTemplate.InstantiateIn(container); break; case WeatherType.Rainy: _rainyTemplate.InstantiateIn(container); break; } } }
これで天気を出すことができました。ただし、これでは日付を出すことができません。現在の仕組みではテンプレートをただ表示することしかできないからです。
WeatherDataItemをaspxに伝える手段がないため、この値を画面に表示させることができません(もちろんCreateChildControlでラベルコントロールを貼ることはできますが、それは論外です)。
そこで、まずはここに日付を追加する方法を考えます。
後ほど詳細を解説しますが、テンプレートからは、そのInstantiateInに渡される引数を参照することができます。この場合はWeatherDayContainerです。
そこで、このWeatherDayContainerにテンプレート内で必要なデータを渡すことからはじめます。この場合は、WeatherDataItemを渡してやって、日付のpublic propertyをつけてやればよいでしょう。
public class WeatherDayContainer : WebControl, INamingContainer { WeatherDataItem _item; public WeatherDayContainer(WeatherDataItem item) { _item = item; } public DateTime Date { get { return _item.Date; } } }
WeatherDayContainerをnewする所でも、WeatherDayItemを渡してやる必要があります。
これで、WeatherDayContainerにDateというpublic propertyができました。次に、これをaspx内から参照します。
<MU:Weather runat="server"> <SunnyTemplate> <%# DataBinder.Eval(Container, "Date", "{0:MM/dd}") %>: 晴れ<br /> </SunnyTemplate> <CloudyTemplate> <%# DataBinder.Eval(Container, "Date", "{0:MM/dd}") %>: 曇り<br /> </CloudyTemplate> <RainyTemplate> <%# DataBinder.Eval(Container, "Date", "{0:MM/dd}") %>: 雨<br /> </RainyTemplate> </MU:Weather>
ここで、Reapeaterコントロールなどでおなじみのデータ連結式、つまり<%#と%>で囲まれた式が登場しました。この式の意味の具体的な意味は次の節で解説します。
このaspxは完全に正しい物ですが、これだけではまだデータ連結式は評価されません。この式が評価されるようにサーバサイドコードを直すには、データ連結式の仕組みを理解する必要があります。
ここで少し寄り道をして、データ連結式の詳細を追ってみましょう。
データ連結式のコンパイル結果を見てみる
それではデータ連結式の仕組みを見ていきます。まずはaspxのコンパイル結果を見てみましょう。
コンパイル結果を見る方法はいろいろありますが、一番単純な方法としてMicrosoft.NET以下に生成されるファイルを直接見ることにします。Visual Web Developersでは、デフォルトの状態でaspxファイルのコンパイル結果が生成されるはずです。
ほかの環境の場合は各自web.configで設定してやる必要があります。詳しくはcompilation要素をMSDNで調べてみてください。
ASP.NETのバージョン次第ですが、私の手元では.NET 2.0が使われているので、検索すべき場所は、以下のとおりです。
%WINDOWS%\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files
このフォルダ以下を、例えば「DataBinder.Eval」で検索してみてください。すると、次のようなコードにぶつかるはずです。
public void @__DataBind__control6(object sender, System.EventArgs e) { Mumurik.Weather.WeatherDayContainer Container; System.Web.UI.DataBoundLiteralControl target; #line 14 "C:\Documents and Settings\arino\My Documents\VisualStudio 2005\WebSites\Weather\Default.aspx" target = ((System.Web.UI.DataBoundLiteralControl)(sender)); #line default #line hidden #line 14 "C:\Documents and Settings\arino\My Documents\Visual
Studio 2005\WebSites\Weather\Default.aspx" Container = ((Mumurik.Weather.WeatherDayContainer)(target.Binding
Container)); #line default #line hidden #line 14 "C:\Documents and Settings\arino\My Documents\Visual
Studio 2005\WebSites\Weather\Default.aspx" target.SetDataBoundString(0, System.Convert.ToString(DataBinder
.Eval(Container, "Date", "{0:MM/dd}"), System.Globalization.CultureInfo
.CurrentCulture)); #line default #line hidden #line 15 "C:\Documents and Settings\arino\My Documents\Visual
Studio 2005\WebSites\Weather\Default.aspx" target.SetDataBoundString(1, System.Convert.ToString(abc, System
.Globalization.CultureInfo.CurrentCulture)); #line default #line hidden }
コメントを削除すると次のようになります。
public void @__DataBind__control6(object sender, System.EventArgs e) { Mumurik.Weather.WeatherDayContainer Container; System.Web.UI.DataBoundLiteralControl target; target = ((System.Web.UI.DataBoundLiteralControl)(sender)); Container = ((Mumurik.Weather.WeatherDayContainer)(target.BindingContainer)); target.SetDataBoundString(0, System.Convert.ToString(DataBinder
.Eval(Container, "Date", "{0:MM/dd}"), System.Globalization.CultureInfo
.CurrentCulture)); target.SetDataBoundString(1, System.Convert.ToString(abc, System
.Globalization.CultureInfo.CurrentCulture)); }
これを見ると、先ほどaspxで書いた<%# DataBinder.Eval(Container, "Date", "{0:MM/dd}") %>という文は、
target.SetDataBoundString(0, System.Convert.ToString(DataBinder.Eval(Container, "Date", "{0:MM/dd}"), System.Globalization.CultureInfo
.CurrentCulture));
に展開されることが分かります。この中で肝になっているのはDataBinder.Eval(Container, "Date", "{0:MM/dd}")です。
さて、RepeaterコントロールやDataGridなどでも使われているこのContainer、そもそも正体は何でしょうか?
その正体は先ほどのcsの中でContainerの定義を見ると分かります。
public void @__DataBind__control6(object sender, System.EventArgs e) { Mumurik.Weather.WeatherDayContainer Container; System.Web.UI.DataBoundLiteralControl target; target = ((System.Web.UI.DataBoundLiteralControl)(sender)); Container = ((Mumurik.Weather.WeatherDayContainer)(target.BindingContainer));
つまり、この記載からContainerは、次のことがわかります。
- 型は
Mumurik.Weather.WeatherDayContainerである - 値は引数の
senderのプロパティに入って渡ってくる
インスタンスが何かは置いといて、WeatherDayContainer型であることは間違いないようです。余談ですが、この型はWeatherコントロールのTemplateContainerアトリビュートで指定したクラスで、インスタンスはInstantiateInに渡した引数です。そのため、この両者がキャスト可能な関係でないと、runtime exceptionが飛びます。
では次に、何をすればこのデータ連結式を有効にできるかを追ってみます。
それには、この関数@__DataBind__control6がどこでどう使われているかを調べてみればよいのです。同じファイル内で検索すると、以下のコードに行き着きます。
@__ctrl.DataBinding += new System.EventHandler(this.@__DataBind__control6);
DataBindingというイベントのイベントハンドラとして登録されるようです。つまり、DataBindingというイベントが来ていないのが、このデータ連結式が評価されない理由であることが推測されます。
このイベントは誰が何をすると起こるのでしょうか?
答えを言ってしまうと、コントロールがDataBind()メソッドを呼ぶと起こります。MSDNのドキュメントを読むと、このメソッドを呼ぶと「その子供のすべてのData Binding Expressionが解決される」と書いてあります。
「解決される」というのは、DataBindingのイベントが起こるということでしょう。ですから、一番上のコントロールで準備が出来たら、一回呼んでやればよいことになります。例えば、WeatherのCreateChildControlsの最後などでよいでしょう。
protected override void CreateChildControls() { foreach (WeatherDataItem item in _dataSource.Weathers) { WeatherDayContainer container = new WeatherDayContainer(item); Controls.Add(container); switch (item.Type) { case WeatherType.Sunny: _sunnyTemplate.InstantiateIn(container); break; case WeatherType.Cloudy: _cloudyTemplate.InstantiateIn(container); break; case WeatherType.Rainy: _rainyTemplate.InstantiateIn(container); break; } } DataBind(); }
これで無事日付が出るようになりました。

少し難しい所なので、この時点のsolutionもつけておきました。サンプルプログラム のWeatherBind.zip(DataBind版)です。
DataBinder.Evalを理解する
ここまでで、aspx内からControlツリーのデータを参照することができるようになりました。機能的にはこの後の話を理解するのに十分ですが、ここで少し寄り道をして、DataBinder.Evalについて調べてみましょう。
Containerの型がWeatherDayContainerであることがもう分かっているので、DataBinder.Evalなんてわざわざ使わなくてもいいのではないでしょうか?
例えば次のEvalの式、
<%# DataBinder.Eval(Container, "Date", "{0:MM/dd}") %>
を次のように変更してみましょう。
<%# Container.Date.ToString("MM/dd") %>
これでも動くことが分かります。しかも、実はaspx内でもインテリセンスが利くため、DataBinder.Eval越しじゃない方が便利なことも多いのです。
ドキュメントを読むと、DataBinder.Evalは「Reflectionを使うので遅い」と書かれています。遅いと書かれていたら測定をするのがエンジニアです。少しここで比較をして見ましょう。
<MU:Weather runat="server"> <SunnyTemplate> <%# Container.Date.ToString("MM/dd") %>: 晴れ<br /> </SunnyTemplate> <RainyTemplate> <%# DataBinder.Eval(Container, "Date", "{0:MM/dd}") %>: 雨<br /> </RainyTemplate> </MU:Weather>
これで、SunnyはDataBinder.Evalを使わず、Rainyは使うようになります。
計測は簡単にTraceにしましょう。「Default.aspx」でTrace="true"を指定して、CreateChildControlsを次のようにします。
protected override void CreateChildControls() { WeatherDataItem sunny = new WeatherDataItem(new DateTime(2006, 7, 3), WeatherType.Sunny); WeatherDataItem rainly = new WeatherDataItem(new DateTime(2006, 7, 4)
, WeatherType.Rainy); Control sunnyParent = new Control(); Control rainyParent = new Control(); Controls.Add(sunnyParent); Controls.Add(rainyParent); for (int i = 0; i < 1000; i++) { WeatherDayContainer sunnyContainer = new WeatherDayContainer(sunny); sunnyParent.Controls.Add(sunnyContainer); _sunnyTemplate.InstantiateIn(sunnyContainer); WeatherDayContainer rainyContainer = new WeatherDayContainer(rainly); rainyParent.Controls.Add(rainyContainer); _rainyTemplate.InstantiateIn(rainyContainer); } Page.Trace.Write("bind sunny begin"); sunnyParent.DataBind(); Page.Trace.Write("bind sunny end"); Page.Trace.Write("bind rainy begin"); rainyParent.DataBind(); Page.Trace.Write("bind rainy end"); }
結果は次のとおりです。
| Eval版 | 通常の参照 |
| 0.2036574 sec | 0.0187753 sec |
確かに結構違いますね。ちなみにRenderはこの場合、約0.6secでした。1,000個でこの値、と言うと、無視してよいか悪いか微妙なラインです。
ソースをつけておきましたので、興味があれば1,000を100や10,000にしたり、平均を取ったりしてみてください。サンプルプログラムの「WeatherProfile.zip」(プロファイル用ソリューション)です。

Studio 2005\WebSites\Weather\Default.aspx"
target = ((System.Web.UI.DataBoundLiteralControl)(sender));
#line