すてきな ASP.NET

ASP.NET 4.0 に備えて

K. Scott Allen

来年 Visual Studio 2010 と .NET Framework 4.0 がリリースされると、ASP.NET 開発者は、Web アプリケーションの構築に向けて、ASP.NET Web Forms フレームワークと ASP.NET MVC フレームワークという、成熟度の高い 2 つのフレームワークを手に入れることになります。どちらも ASP.NET コア ランタイム上に構築され、今後 10 年の幕開けとなる新機能がいくつも搭載されています。

この 2 つのフレームワークにも、基になるランタイムにもさまざまな機能強化が施されているため、1 回のコラムで ASP.NET に追加された機能をすべて紹介することはできません。そこで、今回のコラムでは、中でも重要と思われる Web Forms と MVC の新機能を中心に説明します。

ASP.NET Web Forms の新機能

ASP.NET Web Forms は、マイクロソフトがバージョン 4 をリリースする時点で、開発に着手してから 8 年以上経過することになります。開発チームは、この間、このフレームワークの改良と機能強化に継続して取り組んできました。前回のコラムでは、ASP.NET のコア サービスに含まれるようになった URL ルーティング機能を簡単に使用できる新しいクラスや、フォームのメタタグの内容を容易に制御できる Page 基本クラスの新しい MetaKeywords プロパティと MetaDescription プロパティなどの機能強化についていくつか説明しました。ただし、これらは比較的小さな変更です。

Web Forms に加えられた重要な変更によって、このフレームワークに関する不満がいくつか解消されています。多くの開発者は、Web フォームとそのコントロールから生成される HTML を、その HTML 内部に出力されるクライアント側 ID も含めて、もっときめ細かく制御したいと考えています。ASP.NET 4.0 では、ASP.NET のサーバー側コントロールの多くが作り直され、CSS を使って簡単にスタイルを設定でき、通常の Web 操作に合った HTML を生成するようになりました。また、基本クラスに追加された新しいプロパティを使用して、開発者は、フレームワークが生成するクライアント側 ID をきめ細かく制御できるようにもなりました。ここからは、このような変更点について詳しく説明していきます。

CSS を適用しやすい HTML

CSS を使ってスタイルを設定するのが困難なサーバー コントロールに、ASP.NET のメニュー コントロールがあります。メニューをレンダリングすると、cellpadding、cellspacing、border といった属性を含む table タグが入れ子構造で出力されます。さらに悪いことに、メニュー コントロールは、入れ子になったテーブルのセル内にスタイル情報を埋め込み、ページの先頭にインラインのスタイル ブロックを挿入します。たとえば、次の単純なメニューの定義を見てみましょう。

<asp:Menu runat="server" ID="_menu">
    <Items>
        <asp:MenuItem Text="Home" NavigateUrl="~/Default.aspx" />
        <asp:MenuItem Text="Shop" NavigateUrl="~/Shop.aspx" />
    </Items>
</asp:Menu>

ASP.NET 3.5 では、この単純なメニューから次の HTML が生成されます(わかりやすくするために、いくつかの属性を省略および短縮しています)。

<table class="..." cellpadding="0" cellspacing="0" border="0">
    <tr id="_menun0">
        <td>
            <table cellpadding="0" cellspacing="0" 
                border="0" width="100%">
                <tr>
                    <td style="...">
                        <a class="..." href="Default.aspx">Home</a>
                    </td>
                </tr>
            </table>
        </td>
    </tr>
</table>

ASP.NET 4.0 では、このメニュー コントロールを改訂し、その意味合いからマークアップを生成するようにしました。ASP.NET 4.0 では、先ほどのメニュー コントロールから次の HTML が生成されます。

<div id="_menu">
    <ul class="level1">
        <li><a class="level1" href="Default.aspx" target="">Home</a></li>
    </ul>
</div>

以前のバージョンの ASP.NET でも、コントロール アダプターを使用してコントロールの代替レンダリング ロジックを提供すれば、このような CSS を適用しやすいマークアップを実現できましたが、ASP.NET 4.0 では、既定で CSS を適用しやすいマークアップになります。ASP.NET 3.5 で生成される HTML 用のスタイル シートが既にあり、クライアント スクリプトを作成済みであれば、web.config の pages セクションの controlRenderingCompatibilityVersion 属性を "3.5" に設定すると、先ほどの入れ子になった table を使用するマークアップが生成されます。この属性の既定値は "4.0" です。ASP.NET 4.0 のメニュー コントロールでも、ページの先頭にスタイル ブロックが生成されますが、IncludeStyleBlock プロパティを false に設定すれば生成されなくなります。

ASP.NET 4.0 の他の多くのコントロールも CSS を適用しやすいマークアップを生成します。たとえば、RangeValidator や RequiredFieldValidator のような検証コントロールは、インライン スタイルをレンダリングしなくなり、FormView、Login、Wizard のようなテンプレート コントロールも、table タグ内にコントロール自体をレンダリングしなくなりました (ただし、これらのコントロールの RenderOuterTable プロパティを false に設定した場合のみです)。その他のコントロールも変更されています。1 つだけ例を挙げると、RepeatLayout プロパティを OrderedList 値か UnorderedList 値に設定すると、RadioButtonList コントロールと CheckBoxList コントロールの入力を強制的に List 要素内にレンダリングできます。OrderedList 値を設定すると ol 要素が、UnorderedList 値を設定すると li 要素がそれぞれ強制的にレンダリングされます。

クライアント ID を生成する

クライアント側スクリプトを記述して DOM を操作したことがあれば、クライアント側の ID 属性を変更する際の ASP.NET での密接な関係性についておそらくご存じでしょう。ページ内ですべての ID 属性の一意性を確保するために、ASP.NET ではコントロールの ID プロパティに追加の情報を連結することで、クライアント ID を生成します。サーバーでは、コントロールの ClientID プロパティを使用して、生成されたクライアント ID にアクセスできます。

たとえば、名前付けコンテナー (INamingContainer インターフェイスを実装するコントロールで、ユーザー コントロールやマスター ページで実装されます) 内にコントロールが配置されていると、ASP.NET は名前付けコンテナーの ID をプレフィックスとしてコントロールの ID に追加することによって、ClientID 値を生成します。HTML の繰り返しブロックをレンダリングするデータ バインド コントロールの場合は、連続番号を含むプレフィックスを追加します。ASP.NET ページのソースを表示すると、おそらく "ctl00_content_ctl20_ctl00_loginlink" といった ID 値が確認できます。このように生成される値は、Web フォーム ページのクライアント スクリプトを記述する際の難易度をさらに高くします。

Web Forms 4.0 では、すべてのコントロールに新しく ClientIDMode プロパティが用意されています。このプロパティを使用して、ASP.NET がコントロールの ClientID 値の生成に使用するアルゴリズムを操作できます。値を Static に設定することで、連結やプレフィックスなしに、コントロールの ID を ClientID として使用するよう、ASP.NET に指示します。たとえば、次のコード内の CheckBoxList は、コントロールがページのどこにあるかに関係なく、"checklist" というクライアント ID を含む <ol> タグを生成します。

<asp:CheckBoxList runat="server" RepeatLayout="OrderedList" 
                  ID="checklist" ClientIDMode="Static">
    <asp:ListItem>Candy</asp:ListItem>
    <asp:ListItem>Flowers</asp:ListItem>
</asp:CheckBoxList>

Static モードの ClientIDMode を使用するときは、クライアント ID の一意性を確保する必要があります。ページ内に重複する ID 値が存在すると、事実上、その ID 値を使用して DOM 要素を検索しているすべてのスクリプトが中断します。

ClientIDMode プロパティで使用できる値は、他に 3 種類あります。Predictable 値は、GridView や ListView のような、IDataBoundListControl を実装するコントロールに役立ちます。Predictable 値をこれらのコントロールの ClientIDRowSuffix プロパティと共に使用すると、ID の末尾にサフィックスとして特定の値が追加された状態のクライアント ID が生成されます。たとえば、以下の ListView は、Employee オブジェクトの一覧にバインドされます。各オブジェクトには、employeeList プロパティと IsSalaried プロパティがあります。ClientIDMode プロパティと ClientIDRowSuffix プロパティを組み合わせて、"employeeList_IsSalaried_10" (10 は、関連付けられている従業員の ID を表します) のようなクライアント ID を生成するよう、CheckBox に指示します。

<asp:ListView runat="server" ID="employeeList" 
                      ClientIDMode="Predictable"
                      ClientIDRowSuffix="EmployeeID">
            <ItemTemplate>
                <asp:CheckBox runat="server" ID="IsSalaried" 
                              Checked=<%# Eval("IsSalaried") %> />
            </ItemTemplate>
        </asp:ListView>

ClientIDMode には、Inherit 値も使用できます。既定では、ページ上のすべてのコントロールが、Inherit モードの ClientIDMode を使用します。Inherit は、コントロールが、その親コントロールと同じ ClientIDMode を使用することを意味します。先ほどのコード サンプルでは、CheckBox が、Predictable 値を保持している ClientIDMode 値を ListView から継承しています。ClientIDMode に設定できる 3 つ目の値は、AutoID です。AutoID は、ClientID プロパティを生成する際に、ASP.NET 3.5 で使用されるのと同じアルゴリズムを使用するよう、ASP.NET に指示します。ページの ClientIDMode プロパティの既定値は AutoID です。ページにあるすべてのコントロールは Inherit モードの ClientIDMode プロパティを既定で使用するため、既存の ASP.NET アプリケーションを ASP.NET 4.0 に移行する場合は、ClientIDMode プロパティを変更するまで、ランタイムがクライアントの ID 値を生成する際に使用するアルゴリズムが変更されません。このプロパティを web.config の pages セクションで設定して、アプリケーション内のすべてのページに異なる既定値を指定することもできます。

新しいプロジェクト テンプレート

Visual Studio 2008 の Web アプリケーションや Web サイトのプロジェクト テンプレートでは、Default.aspx ページ、web.config ファイル、および App_Data フォルダーが提供されます。作業の出発点となるこれらのテンプレートはシンプルで、実際のアプリケーションの開発を始める前に、いくつか追加作業が必要です。Visual Studio 2010 の同様のテンプレートでは、最新の手法を使用するアプリケーションをビルドする際に必要な、より多くのインフラストラクチャが提供されます。図 1 は、これらのテンプレートを使って作成したまったく新しいアプリケーションのスクリーン ショットです。

New Web Application in Visual Studio 2010

図 1 Visual Studio 2010 の新しい Web アプリケーション

新しいアプリケーションには、マスター ページ (Site.master) が既定で含まれているのがわかります。新しいプロジェクト内にあるすべての .aspx ファイルは、ContentPlaceholder コントロールを使用してマスター ページで定義される構造にコンテンツをプラグインする、コンテンツ ページになります。また、新しいプロジェクトの Content ディレクトリには、スタイル シート (Site.css) も含まれています。マスター ページは、link タグを使用して、このスタイル シートをインクルードします。このスタイル シートには、ページ本文、見出し、主要レイアウトなど、ページの外観を制御するスタイルがいくつか定義されています。新しいプロジェクトには、最新バージョンの jQuery ライブラリを含む Scripts ディレクトリもあります。jQuery ライブラリは、マイクロソフトが公式にサポートするオープン ソースの JavaScript フレームワークで、インストールの一部として Visual Studio 2010 に含まれます。

新しいプロジェクト テンプレートと併せて、このマスター ページとスタイル シートを使用すると、開発者は Web フォームを使った作業を適切に進めることができるようになります。図 2 は、この新しいアプリケーションを実行したところを示しています。Visual Studio 2010 には、Web サイトと Web アプリケーション用の "空の" テンプレートもあります。これらの空のテンプレートには、ファイルやディレクトリが含まれていないため、アプリケーションをまったく最初から作成することになります。

Running the new ASP.NET Application

図 2 新しい ASP.NET アプリケーションの実行

ASP.NET 4.0 の新しいプロジェクトに関するもう 1 つの朗報は、web.config ファイルが最初はほぼ空に近い状態になっていることです。ASP.NET 3.5 の web.config ファイルで見慣れていたほとんどの構成は、.NET Framework 4.0 のインストール ディレクトリの下にある machine.config ファイルに含まれるようになりました。machine.config ファイルには、System.Web.Extensions ディレクトリにあるコントロールの構成、Web サービスで JavaScript プロキシをサポートするために構成する HTTP ヘッダーや HTTP モジュール、IIS 7 の配下で実行される Web サイト向けの system.webserver セクションなどがあります。

ASP.NET MVC の新機能

Visual Studio 2010 では、ASP.NET MVC フレームワークの 2 回目のリリースが行われます。このフレームワークの歴史はまだ浅いですが、テストの容易性を高めるようにデザインされたフレームワークを求める多くの Web 開発者の注目を集めました。ASP.NET MVC の 2 回目のリリースでは、開発者の生産性向上と、エンタープライズ規模の大規模プロジェクトに対処するためのインフラストラクチャへの追加に重点が置かれます。

Area (区分)

非常に大規模な ASP.NET Web フォーム アプリケーションを構築する 1 つの手法は、アプリケーションを複数のサブ プロジェクトに分割することです。これは、Patterns and Practices (P&P) の Web Client Composite Library (Web クライアント複合ライブラリ) によって推し進められている手法です。この手法はいくつかの MVC 標準に反しているため、ASP.NET MVC 1.0 で使用するのは困難です。MVC 2.0 では、"区分" という考え方を使用してこうしたシナリオを公式にサポートします。"区分" を使用すると、1 つの MVC アプリケーションを、複数の Web アプリケーション プロジェクトや 1 つのプロジェクト内の複数のディレクトリにパーティション分割できます。また、同じアプリケーションの論理的に異なる部分を切り離して、保守性を高めることもできます。

MVC Web アプリケーションの親区分は、そのアプリケーションの global.asax ファイルとルート レベルの web.config ファイルを含む 1 つの MVC プロジェクトです。親区分には、アプリケーション全体のスタイル シート、JavaScript ライブラリ、マスター ページといった共通に使用するコンテンツを含めることもできます。子区分も MVC Web アプリケーション プロジェクトですが、これらのプロジェクトは実行時に親区分のプロジェクトの下に物理的に存在するため、親とその子は 1 つのアプリケーションとして表示されます。

たとえば、大規模な在庫管理アプリケーションがあるとします。このアプリケーションは、親区分に加えて、注文、配送、報告、管理といった区分に分割できます。各区分を個別の MVC Web プロジェクトにすることができます。各プロジェクトでは、抽象基本クラス AreaRegistration から派生するクラスを含めて、その区分までの経路を登録します。次のコードでは、AreaName プロパティをオーバーライドして報告区分のフレンドリ名を返し、RegisterArea メソッドをオーバーライドして報告区分で使用できる経路を定義しています。

public class ReportingAreaRegistration : AreaRegistration
{
    public override string AreaName
    {
        get { return "Reporting"; }
    }

    public override void RegisterArea(AreaRegistrationContext context)
    {
        context.MapRoute(
            // route name
            "ReportingDefault",
            // url pattern
            "reporting/{controller}/{action}",
            // route defaults
            new { controller = "Home", action = "Index" },
            // namespaces
            new string[] { "Reporting.Controllers" });            
    }
}

報告区分のコントローラーを検索するときに、検索する名前空間を文字列の配列として含めています。検索する名前空間を制限することで、さまざまな区分で同じ名前のコントローラーを使用できます (たとえば、1 つのアプリケーション内で複数の HomeController クラスを使用できます)。

DataAnnotations を使用して検証を容易にする

ASP.NET MVC の DefaultModelBinder は、リクエスト環境からモデルのプロパティにデータを移動します。たとえば、モデル バインダーが、Title というプロパティを持つモデル オブジェクトを確認するときは、フォーム、クエリ文字列、およびサーバー変数を使って、名前 (Title) が一致する変数を検索します。ただし、モデル バインダーが行う検証は、単純な型表記チェックだけです。モデル オブジェクトの Title プロパティに 50 文字以下の文字列のみを含める場合は、モデルにカスタム モデル バインダーを実装するか、IDataErrorInfo インターフェイスを実装して、コントローラー アクションの実行中にこの検証チェックを行う必要があります。

ASP.NET MVC 2.0 の DefaultModelBinder は、モデル オブジェクトの DataAnnotation 属性を確認します。これらの DataAnnotation 属性を使えば、モデルの検証制約を指定できます。たとえば、次の Movie クラスについて考えてみましょう。

public class Movie
{
    [Required(ErrorMessage="The movie must have a title.")]
    [StringLength(50, ErrorMessage="The movie title is too long.")]
    public string Title { get; set; }
}

この Title プロパティに付けられた属性では、Title が必須フィールドであることと、文字列の最大長が 50 文字であることをモデル バインダーに指示しています。検証に失敗すると、MVC フレームワークが ErrorMessage のテキストをブラウザーに自動的に表示します。他にも組み込みの検証属性として、範囲をチェックする属性や、正規表現に一致させる属性などもあります。

このコラムの執筆時点では、MVC のランタイムが検証属性を使用するのはサーバー側の検証チェックのみです。MVC チームは、MVC 2.0 のリリース時には、検証属性からクライアント側の検証ロジックを生成できると考えています。これらの属性を使用してサーバー側でもクライアント側でも検証を実行すれば、アプリケーションの保守性が大きく向上します。

テンプレート化されたヘルパー

ASP.NET MVC 2.0 のテンプレート化されたヘルパーも DataAnnotation 属性を利用します。しかし、テンプレート ヘルパーは、検証ロジックを目的とする属性ではなく、モデルの UI 表示を目的とする属性を使用します。最初のテンプレート ヘルパーは、新しい HTML ヘルパー メソッドの DisplayFor と EditorFor です。これらのヘルパー メソッドは、モデルの型に基づいて、特定のモデルのテンプレートを検索します。たとえば、先ほどの Movie クラスを使用してみましょう。ただし、次のプロパティを追加します。

public class Movie
{        
    // ...

    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
}

このシナリオでは、すべての映画に公開日が含まれます。ただし、今回は映画の公開時間は使用しません。このプロパティを表示するときは日付情報だけを表示し、時間情報は表示しません。このプロパティは、DataType 属性で修飾されています (この属性名は、今回の目的を表しています)。

公開日を正しく表示するには、表示テンプレートが必要です。表示テンプレートは、DisplayTemplates フォルダー内にある .ascx 拡張子が付いたファイルで、ビューの一部を表しています。DisplayTemplates フォルダー自体は、コントローラーのビュー フォルダーの下に置く (この場合、テンプレートはその 1 つのコントローラーのビューのみに適用されます) か、共有ビュー フォルダーに置きます (この場合は、テンプレートはすべてのビューに使用できます)。この例では、Date.ascx というテンプレートが必要で、コードは次のようになります。

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%= Html.Encode(String.Format("{0:d}", Model)) %>

MVC フレームワークがこのテンプレートを使用するには、ReleaseDate プロパティをレンダリングするときに DisplayFor ヘルパー メソッドを使用する必要があります。図 3 のコードは、別のテンプレート (Movie.ascx 表示テンプレート) のものです。

図 3 Movie.ascx 表示テンプレート

<%@ Control Language="C#" 
    Inherits="System.Web.Mvc.ViewUserControl<Movie>" %>

    <fieldset>
        <legend>Fields</legend>
        <p>
            Title:
            <%= Html.LabelFor(m => m.Title) %>
            <%= Html.DisplayFor(m => m.Title) %>
        </p>
        <p>
            <%= Html.LabelFor(m => m.ReleaseDate) %>
            <%= Html.DisplayFor(m => m.ReleaseDate) %>
        </p>
    </fieldset>

LabelFor と DisplayFor の各ヘルパー メソッドは厳密に型指定されています。これにより、モデルがリファクタリングされる場合に変更を反映できます。Movie.ascx テンプレートを使用して、アプリケーションの任意の場所に映画を表示する場合は、DisplayFor ヘルパー メソッドを繰り返し使用するだけです。Movie クラスに対して厳密に型指定されているビューのコードを次に示します。

<asp:Content ID="detailContent" 
             ContentPlaceHolderID="MainContent" 
             runat="server">                    
        Movie:
        <%= Html.DisplayFor(m => m) %>
        
    </p>
</asp:Content>

DisplayFor メソッドは、ビュー ページと同じモデルを使用するよう厳密に型指定されているため、DisplayFor ラムダ式内の m パラメーターは、Movie 型になります。DisplayFor は、映画を表示するときに、Movie.ascx テンプレートを自動的に使用します (次の DisplayFor は Date.ascx テンプレートを検索します)。映画の ReleaseDate プロパティで DataType 属性を使用しなかった場合、DisplayFor は Date.ascx テンプレートを使用しないで、ReleaseDate の日付と時間を表示しますが、DataType 属性を使用すると、フレームワークが適切なテンプレートを使用するようになります。厳密な型指定、入れ子になっているテンプレート、およびデータ型の注釈の概念はとても強力なので、これらによって生産性が向上することがわかるでしょう。

K. Scott Allen は Pluralsight 技術スタッフのメンバーであり、OdeToCode の創設者です。Scott の連絡先は、scott@OdeToCode.com (英語のみ) で、ブログは odetocode.com/blogs/scott (英語) です。

この記事のレビューに協力してくれた技術スタッフの Phil Haack と Matthew Osborn に心より感謝いたします。

ご質問やご意見は xtrmasp@microsoft.com (英語のみ) までお送りください。