何をテンプレートにして何をデータ連結式で公開するか?
ここからは、このITemplateを用いた時の設計について、実際に例を示して比較検討してみたいと思います。
まず、晴れ、雨、曇りをそれぞれpngとして表示するように変更してみましょう。ついでにそれらの画像をテーブルでレイアウトすることにします。
<h3>今週の天気</h3> <table> <tr> <MU:Weather runat="server"> <SunnyTemplate> <td> <table border="1"> <tr><td><%# DataBinder.Eval(Container, "Date","{0:MM/dd}")%></td><td> 晴れ</td></tr> <tr><td colspan="2"><img src="sunny.png" />
</td></tr> </table> </td> </SunnyTemplate> <CloudyTemplate> <td> <table border="1"> <tr><td><%# DataBinder.Eval(Container, "Date",
"{0:MM/dd}")%></td><td> 曇り</td></tr> <tr><td colspan="2"><img src="cloudy.png" />
</td></tr> </table> </td> </CloudyTemplate> <RainyTemplate> <td> <table border="1"> <tr><td><%# DataBinder.Eval(Container, "Date",
"{0:MM/dd}")%></td><td> 雨</td></tr> <tr><td colspan="2"><img src="rainy.png" />
</td></tr> </table> </td> </RainyTemplate> </MU:Weather> </tr> </table>
ヒトデのように見えるのは、太陽のつもりです(絵心がなくて申し訳ありません)。
絵心さえ気にしなければ、この位の変更ならコードを一切変更せずに対応できます。この場合、3種類のテンプレートを外から指定できるようにする、という方針です。これを今後「一本化する前のバージョン」と呼びましょう。
この時点のsolutionもつけておきました。サンプルプログラムの「WeatherImage.zip」(Image+Table版)です。
これはこれでよいのですが、もう少し中身をControlのcs側に移す場合も検討してみます。
上記aspxを見てみると分かりますが、SunnyTemplate、CloudyTemplate、 RainyTemplateは極めて似ています。違うのは、「晴れ、曇り、雨」の文字列とimgのURLです。どうせなら、この変化する部分を、すべてデータ連結式越しで与えるコードとして考えてみましょう。テンプレートは次のようになります。
<MU:Weather runat="server"> <WeatherTemplate> <td> <table border="1"> <tr><td><%# DataBinder.Eval(Container, "Date","{0:MM/dd}")%></td><td><%# DataBinder.Eval(Container, "Weather") %>
</td></tr> <tr><td colspan="2"><img src='<%# DataBinder.Eval
(Container, "URL") %>' /></td></tr> </table> </td> </WeatherTemplate> </MU:Weather>
cs側では、まずURLとWeatherというプロパティを追加する必要があります。
public class WeatherDayContainer : WebControl, INamingContainer ... public string Weather { get { switch (_item.Type) { case WeatherType.Sunny: return "晴れ"; case WeatherType.Cloudy: return "曇り"; case WeatherType.Rainy: return "雨"; default: return "晴れ"; // なぜかVisual Studio Web Developersはこれがないと怒る… } } } public string URL { get { switch (_item.Type) { case WeatherType.Sunny: return "sunny.png"; case WeatherType.Cloudy: return "cloudy.png"; case WeatherType.Rainy: return "rainy.png"; default: return "sunny.png"; } } } }
他人に対するswitchはいつでも間違い、とマーチンファウラーも言っておりますが、説明の主体ではないのでこのままで行きます。
あとは3つのテンプレートに分けていたCreateChildControlsを一つにまとめるだけです。
public class Weather : WebControl ... private ITemplate _weatherTemplate; [TemplateContainer(typeof(WeatherDayContainer))] public ITemplate WeatherTemplate { set { _weatherTemplate = value; } } protected override void CreateChildControls() { foreach (WeatherDataItem item in _dataSource.Weathers) { WeatherDayContainer container = new WeatherDayContainer(item); Controls.Add(container); _weatherTemplate.InstantiateIn(container); } DataBind(); } }
以上でSunnyTemplate、CloudyTemplate、RainyTemplateをWeatherTemplateに一本化できました。
これを今後「一本化した後のバージョン」と呼ぶことにします。これが今回の最終版です。サンプルプログラムは、「WeatherOneTemplate.zip」(テンプレート1本化版)です。
両者のトレードオフの検討
一本化する前のバージョンと、一本化後のバージョンを比較してみます。
一本化する前は、複数のテンプレートを外部に公開していました。使う側の作業が多くなりますが、柔軟性は高くなります。また、aspxの記述が、より生成されるhtmlに近い形になります。
一本化した後は、逆にCalendarDayContainerで公開されているプロパティベースでしかテンプレートが書けません。その代わり、aspxの記述はより簡潔になります。例えば、天気によるswitchをテンプレートからCalendarDayContainerに移した形になります。switchをDataSourceに移せば、オブジェクトにより自分自身の判断をさせることが可能となります。
新しい天気として、「雷」が追加されたとしましょう。一本化してあれば、aspx側の変更は必要ありません。Data AccessのレイヤーにURLやWeatherも移しておけば、Data Accessのレイヤーだけで天気の種類の変更に対応できます。この点が一本化するメリットでしょう。
一方で、このコントロールのユーザーが、例えば雨の時だけcssのクラスを変更したい、と思った場合には、複数テンプレートに軍配が上がります。テンプレートが別々なら、次のように指定するだけ済むからです。
<RainyTemplate> <td> <table class="rainy" border="1"> <tr><td><%# DataBinder.Eval(Container, "Date", "{0:MM/dd}")%></td><td> 雨</td></tr> <tr><td colspan="2"><img src="rainy.png" /></td></tr> </table> </td> </RainyTemplate>
ところが、一本化してあるとCalendarDayContainerがCSSのクラス名を表すプロパティを公開していなくてはいけません。
この場合、aspxではなくてコントロールの実装に手を入れる必要があります。つまり、このコントロールのユーザーはコントロールに手を入れないと、達成できないことが増えるわけです。
以上から、コントロール側の変更に対してClosedであることを望むなら、データ連結式経由でhot spotを入れてテンプレートは一本化します。
逆にaspxレイヤーでの変更に対してよりOpenにしたい場合は、hot spotとしては複数のテンプレートがあることが望ましい、といえるでしょう。
教科書的には一本化を推奨するのでしょうけれど、実務では状況に応じて最適な選択は変わる、というのが私の考えです。
余談ですが、コントロールのカスタマイズの口としては、(XMLの意味での)attributeがあることも忘れてはいけません。
Repeaterコントロールのデザイン
実際の例を確認するために、ASP.NETのRepeaterコントロールのメタデータを見てみましょう。ITemplateの部分だけ抜き出してみます。
public class Repeater ... public virtual ITemplate AlternatingItemTemplate { get; set; } public virtual ITemplate ItemTemplate { get; set; } public virtual ITemplate FooterTemplate { get; set; } public virtual ITemplate HeaderTemplate { get; set; } public virtual ITemplate SeparatorTemplate { get; set; }
Repeaterの場合、Weatherとは事情が違いますが、かなりたくさんのITemplateを公開していることが分かります。
データ連結式越しのアクセスの方はどうでしょうか? ItemTemplateのTemplateContainerを見てみると、次のようになっています。
[TemplateContainer(typeof(RepeaterItem))] public virtual ITemplate ItemTemplate { get; set; }
また、RepeaterItemのメタデータを確認すると、次のようになっています。
public class RepeaterItem : Control, IDataItemContainer, INamingContainer { public virtual object DataItem { get; set; } public virtual int ItemIndex { get; } public virtual ListItemType ItemType { get; } }
このうち、ListItemTypeはenumで、上記テンプレートとほぼ同じ選択肢があることが分かります。
public enum ListItemType { Header = 0, Footer = 1, Item = 2, AlternatingItem = 3, SelectedItem = 4, EditItem = 5, Separator = 6, Pager = 7, }
皆から使われる共通のコントロール、という性格からだと思いますが、Templateもデータ連結式越しの選択肢もかなり多いことが分かります。
まとめ
TemlateContainerとして指定するコントロールは、データ連結式からアクセスできます。この仕組みを利用して、aspxからコントロールのデータを参照できます。
今回は、前の記事と合わせて、aspxからコントロールに情報を渡すITemplateと、コントロールからaspxに情報を渡すデータ連結式という2つのパスがあることを紹介しました。少々難しい箇所があったかもしれませんが、業務に応用していただければ幸いです。

"{0:MM/dd}")%>