SHOEISHA iD

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

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

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

ITemplateでコントロールのプレゼンテーションを分離する

ITemplateでコントロールのプレゼンテーションを分離する(後編)

データ連結式でaspxからコントロールツリーのデータを参照する


何をテンプレートにして何をデータ連結式で公開するか?

 ここからは、このITemplateを用いた時の設計について、実際に例を示して比較検討してみたいと思います。

 まず、晴れ、雨、曇りをそれぞれpngとして表示するように変更してみましょう。ついでにそれらの画像をテーブルでレイアウトすることにします。

imageとtable化1
<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を見てみると分かりますが、SunnyTemplateCloudyTemplateRainyTemplateは極めて似ています。違うのは、「晴れ、曇り、雨」の文字列と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側では、まずURLWeatherというプロパティを追加する必要があります。

データ連結式向けに2つプロパティを追加
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を一つにまとめるだけです。

WeatherTemplateに一本化
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();
    }
}

 以上でSunnyTemplateCloudyTemplateRainyTemplateWeatherTemplateに一本化できました。

 これを今後「一本化した後のバージョン」と呼ぶことにします。これが今回の最終版です。サンプルプログラムは、「WeatherOneTemplate.zip」(テンプレート1本化版)です。

両者のトレードオフの検討

 一本化する前のバージョンと、一本化後のバージョンを比較してみます。

 一本化する前は、複数のテンプレートを外部に公開していました。使う側の作業が多くなりますが、柔軟性は高くなります。また、aspxの記述が、より生成されるhtmlに近い形になります。

 一本化した後は、逆にCalendarDayContainerで公開されているプロパティベースでしかテンプレートが書けません。その代わり、aspxの記述はより簡潔になります。例えば、天気によるswitchをテンプレートからCalendarDayContainerに移した形になります。switchDataSourceに移せば、オブジェクトにより自分自身の判断をさせることが可能となります。

 新しい天気として、「雷」が追加されたとしましょう。一本化してあれば、aspx側の変更は必要ありません。Data AccessのレイヤーにURLWeatherも移しておけば、Data Accessのレイヤーだけで天気の種類の変更に対応できます。この点が一本化するメリットでしょう。

 一方で、このコントロールのユーザーが、例えば雨の時だけcssのクラスを変更したい、と思った場合には、複数テンプレートに軍配が上がります。テンプレートが別々なら、次のように指定するだけ済むからです。

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の部分だけ抜き出してみます。

Repeaterコントロール
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を公開していることが分かります。

 データ連結式越しのアクセスの方はどうでしょうか? ItemTemplateTemplateContainerを見てみると、次のようになっています。

RepeaterのITempTemlate
[TemplateContainer(typeof(RepeaterItem))]
public virtual ITemplate ItemTemplate { get; set; }

 また、RepeaterItemのメタデータを確認すると、次のようになっています。

RepeaterItemのメタデータ
public class RepeaterItem : Control, IDataItemContainer, INamingContainer
{
    public virtual object DataItem { get; set; }
    public virtual int ItemIndex { get; }
    public virtual ListItemType ItemType { get; }
}

 このうち、ListItemTypeenumで、上記テンプレートとほぼ同じ選択肢があることが分かります。

ListItemTypeのメタデータ
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つのパスがあることを紹介しました。少々難しい箇所があったかもしれませんが、業務に応用していただければ幸いです。

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

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
ITemplateでコントロールのプレゼンテーションを分離する連載記事一覧
この記事の著者

mumurik(ムムリク)

最近の本業はASP.NET上でのパッケージアプリケーション開発。WebControlとjsのコードが半分づつくらい。

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

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

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/515 2006/09/22 09:56

イベント

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

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

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

メールバックナンバー