ASP.NET 動的データ

5 分で構築するデータ駆動型エンタープライズ Web サイト

James E. Henry

長年、開発者は、UI からの作成、読み取り、更新、削除 (CRUD: Create、Read、Update、Delete) 機能を備えたデータ層を構築するという面倒な作業に取り組んできました。個人的には 10 年以上前のプロジェクトを思い出します。このプロジェクトでは、Rational Rose オブジェクト モデルからビジネス層とデータ層を自動生成するコードを書かなければなりませんでした。これは大変な作業でした。

その数年後、マイクロソフトは Microsoft .NET Framework と DataSet オブジェクトをリリースしました。データセット デザイナーを使用すると、厳密に型指定された DataSet を SQL Server などのバックエンド データベースに接続して、アプリケーションの実行中にいつでも DataSet を操作できます。その結果、直接 SQL を操作する必要性は最小限に抑えられますが、データベースのデータを表示する Web ページは依然として手作業で作成しなければなりません。

さらに数年後、マイクロソフトは Entity Framework をリリースします。このオブジェクト リレーショナル マッピング (ORM) フレームワークでは、LINQ を使用してデータベース スキーマを抽象化し、概念スキーマとしてこれをアプリケーションに提供します。DataSet と同様、このテクノロジでも SQL を直接操作する必要性が最小限に抑えられます。しかし、やはりデータを表示する Web ページは手作業で作成しなければなりません。

そして現在、マイクロソフトは、ASP.NET 動的データをリリースしています。これは、Entity Framework と ASP.NET ルーティングを組み合わせたもので、物理的には存在しない URL にアプリケーションから応答できるようにします。これらの機能を使用すると、わずか数分で、運用可能なデータ駆動型 Web サイトを作成できます。ここでは、その方法を説明します。

はじめに

シナリオの設定として、Adventure Works という架空企業のイントラネット Web サイトを構築するとします。この Web サイトでは、社員情報の管理を可能にします。

会社のデータは、Microsoft SQL Server 2008 データベースに格納されています (このデータベースは msftdbprodsamples.codeplex.com (英語) からダウンロードしてインストールできます)。

Microsoft Visual Studio 2010 を開き、新しい ASP.NET 動的データ エンティティ Web サイトの C# プロジェクトを作成します。

ASP.NET 動的データ エンティティ Web サイト プロジェクトでは、ASP.NET ルーティングと Entity Framework を利用して、すばやくデータ駆動型 Web サイトを作成できます。この機能を利用するには、Entity Framework データ モデルをプロジェクトに追加する必要があります。それには、[Web サイト] メニューの [新しい項目の追加] をクリックします。[新しい項目の追加] ダイアログ ボックスで、[ADO.NET Entity Data Model] を選択します。これに HumanResources.edmx という名前を付けます。

このモデルを App_Code フォルダーに追加するよう求めるメッセージが表示されるので、[はい] をクリックして、この変更を適用します。Web サイト プロジェクトを作成すると、Visual Studio によって App_Code フォルダーに格納されているすべてのコードが動的にコンパイルされます。Entity Data Model の場合、データ コンテキストの部分クラスとエンティティの部分クラスが、Visual Studio によって自動生成されます。この例では、HumanResources.Designer.cs というファイルにコードが保存されます。

次に、Entity Data Model ウィザードが表示されます (図 1 参照)。[データベースから生成] を選択し、[次へ] をクリックします。

image: Starting the Entity Data Model Wizard

図 1 Entity Data Model ウィザードの開始

ここで、Adventure Works データベースへの接続を選択します。接続が存在しなければ、新しい接続を作成します。図 2 は、Dev\BlueVision という SQL Server インスタンスでの AdventureWorks への接続です。

image: Configuring the Data Connection

図 2 データ接続の構成

次のページでは、Human Resources スキーマのすべてのテーブルを選択できます。スキーマ名がかっこ内に表示されます。図 3 では、テーブルをいくつか選択しています。

image: Selecting Tables for the Human Resources Schema in Visual Studio

図 3 Visual Studio での Human Resources スキーマのテーブルの選択

[完了] をクリックすると、プロジェクト用に選択したテーブルを基に、Visual Studio によってエンティティが自動生成されます。図 4 は、Visual Studio によってデータ スキーマから生成された 7 つのエンティティです。Visual Studio は、データベースの外部キー制約を使用して、エンティティ間のリレーションシップを作成します。たとえば、Employee エンティティと JobCandidate エンティティとの間には、一対多リレーションシップがあります。つまり、1 人の社員は、社内の複数の職務の候補者になることができます。

iamge: Entities Generated from Database Tables in Visual Studio

図 4 Visual Studio のデータベース テーブルから生成されたエンティティ

また、EmployeeDepartmentHistory エンティティは、Employee エンティティと Department エンティティとを結合します。EmployeeDepartmentHistory テーブルに Employee テーブルと Department テーブルの結合に必要なフィールドしか含まれていなかったならば、EmployeeDepartmentHistory エンティティは単純に省略されていたでしょう。その場合は、Employee エンティティと Department エンティティ間での直接操作が可能になります。

ASP.NET ルーティングを使用する

ASP.NET ルーティングは、物理的に存在しない URL にアプリケーションから応答できるようにします。たとえば、http://mysite/Employee と http://mysite/Department の 2 つの URL が、http://mysite/Template.aspx のページにリダイレクトされる可能性があるとします。このとき、ページ自体で URL から情報を抽出し、社員一覧と部門一覧のどちらを表示するかを特定できます。どちらのビューにも同じ表示テンプレートが使用されます。

ルートは、ASP.NET HTTP ハンドラーにマップされる URL パターンにすぎません。実際の URL を変換後のパターンにどのようにマップするかは、ハンドラーによって決定されます。ASP.NET 動的データでは、URL パターンの {table} および {action} というプレースホルダーを変換する DynamicDataRouteHandler というルート ハンドラーが使用されます。たとえば、ハンドラーでは次の URL パターンを使用できるとします。

http://mysite/{table}/{action}.aspx

このパターンを使用して、http://mysite/Employee/List.aspx という URL が解釈されるとします。

この場合、Employee は {table} プレースホルダーとして処理され、List は {action} プレースホルダーとして処理されます。この処理が終わると、ハンドラーは Employee エンティティのインスタンス一覧を表示できます。DynamicDataRouteHandler は {table} プレースホルダーを使用して、表示するエンティティの名前を特定し、{action} パラメーターを使用して、エンティティの表示に使用するページ テンプレートを特定します。

Global.asax ファイルには、下記のように、アプリケーションの初回起動時に呼び出される RegisterRoutes という静的メソッドが含まれています。

public static void RegisterRoutes(RouteCollection routes) {
  // DefaultModel.RegisterContext(typeof(YourDataContextType), 
  //   new ContextConfiguration() { ScaffoldAllTables = false });
  routes.Add(new DynamicDataRoute("{table}/{action}.aspx") {
    Constraints = new RouteValueDictionary(
    new { action = "List|Details|Edit|Insert" }),
    Model = DefaultModel
  });
}

最初に必要な作業は、DefaultModel instance の RegisterContext メソッドを呼び出すコードのコメントを解除することです。このメソッドは、Entity Framework データ コンテキストを ASP.NET 動的データに登録します。このコード行は次のようになります。

DefaultModel.RegisterContext(
    typeof(AdventureWorksModel.AdventureWorksEntities), 
    new ContextConfiguration() { ScaffoldAllTables = true });

最初のパラメーターは、データ コンテキストの型を指定します。この例では、AdventureWorksEntities です。ScaffoldAllTables プロパティを true に設定することで、すべてのエンティティをサイトに表示できるようにします。このプロパティを false に設定した場合は、サイトに表示する各エンティティに ScaffoldTable(true) 属性を適用する必要があります (実際には、ScaffoldAllTables プロパティを false に設定して、データベース全体が誤ってエンド ユーザーに公開されないようにします)。

RouteCollection クラスの Add メソッドは、新しい Route インスタンスをルート テーブルに追加します。ASP.NET 動的データを使用すると、Route インスタンスは、実際には DynamicDataRoute インスタンスになります。DynamicDataRoute クラスはハンドラーとして DynamicDataRouteHandler を内部で使用します。
DynamicDataRoute のコンストラクターに渡すパラメーターは、ハンドラーが物理 URL の処理に使用するパターンを表します。定数も設定して、{action} の値を List、Details、Edit、Insert に制限します。

理論上は、ASP.NET 動的データの使用に必要な作業は、これだけです。このアプリケーションをビルドして実行すると、図 5 のようなページが表示されます。

image: A Basic ASP.NET Dynamic Data Site

図 5 基本の ASP.NET 動的データ サイト

メタデータをサポートする

気を付けなければならないのは、エンティティが表すテーブルの名前と、エンティティの名前が同じであることです。これはコードやデータベース内では問題ないかもしれませんが、通常、UI では問題になります。

ASP.NET 動的データでは、表示されたエンティティを表すクラスに DisplayName 属性を適用することで、そのエンティティの名前を簡単に変更できます。エンティティ クラスは Visual Studio が自動生成したファイルに含まれているため、部分クラスのコードを変更する場合は別のコード ファイルで行います。そこで、Metadata.cs という新しいコード ファイルを App_Code フォルダーに追加します。追加したら、次のコードをそのファイルに追加します。

using System;
  using System.Web;
  using System.ComponentModel;
  using System.ComponentModel.DataAnnotations;

  namespace AdventureWorksModel {
    [DisplayName("Employee Addresses")]
    public partial class EmployeeAddress { }
  }

アプリケーションをリビルドして実行します。EmployeeAddresses が、今度は Employee Addresses になります。

同様に、EmployeeDepartmentHistories、EmployeePayHistories、および JobCandidates の名前も、適切な名前に変更します。

次に、[Shifts] リンクをクリックします。社員のシフト一覧が表示されます。ここでは、StartTime と EndTime をそれぞれ Start Time と End Time に名前変更します。

新たな問題は、Start Time の値と End Time の値のどちらにも日付と時刻が表示されることです。今回の状況では、実際にユーザーが確認する必要があるのは時間のみなので、Start Time と End Time の値の書式を設定して、時刻のみが表示されるようにします。DataType という属性を使用すると、EmailAddress や Time など、特定のフィールドに具体的なデータ型を指定できます。適用対象のフィールドに DataType 属性を適用します。

まず、Metadata.cs ファイルを開き、次のクラス定義を追加します。

[MetadataType(typeof(ShiftMetadata))]
public partial class Shift { }
public partial class ShiftMetadata { }

MetadataType という属性が Shift クラスに適用されています。この属性を使用すると、エンティティのフィールド用の追加メタデータを保持している別のクラスを指定できます。同じクラス メンバーを複数の部分クラスに追加することはできないため、追加メタデータを直接 Shift クラスのメンバーには適用できません。たとえば、HumanResources.Designer.cs において定義されてる Shift クラスには、既に StartTime というフィールドがあります。したがって、Metadata.cs で定義されている Shift クラスに同じフィールドは追加できません。また、HumanResources.Designer.cs は Visual Studio が生成するため、手動で変更しないでください。

MetadataType 属性を使用すると、メタデータをフィールドに適用できるよう、完全に異なるクラスを指定できます。次のコードを ShiftMetadata クラスに追加します。

[DataType(DataType.Time)]
  [Display(Name = "Start Time")]
  public DateTime StartTime { get; set; }

DataType 属性では、Time 列挙値を指定して、StartTime フィールド が時刻値として書式設定されるようにします。その他の列挙値には、PhoneNumber、Password、Currency、EmailAddress などがあります。Display 属性では、フィールドが一覧として表示される場合にフィールドの列として表示するテキストと、編集または読み取り専用モードで表示する場合に使われるフィールドのラベルを指定します。

では、アプリケーションをリビルドして実行してみましょう。図 6 は、[Shifts] リンクをクリックした場合の結果です。

image: Revised Shifts Page Using MetadataType Definitions

図 6 MetadataType の定義を使用して変更した [Shifts] ページ

同様のコードを追加して、EndTime フィールドの外観を変更できます。

ここで、[JobCandidates] を見てみましょう。Employee 列には、NationalIDNumber フィールドの値が表示されています。これは、このままでは役に立ちません。Employee データベース テーブルには社員名のフィールドはありませんが、社員のログイン情報のフィールドとして LoginID があります。このフィールドは、このサイトのユーザーにとってより有用な情報を提供できます。

そのためには、再びメタデータ コードを変更して、すべての Employee 列に LoginID フィールドの値が表示されるようにします。Metadata.cs ファイルを開き、次のクラス定義を追加します。

[DisplayColumn("LoginID")]
public partial class Employee { }

DisplayColumn 属性は、エンティティのインスタンスを表すために使用するエンティティのフィールド名を指定します。図 7 は、LoginID が表示された新しい一覧です。

image: Identifying Employees with LoginIDs

図 7 LoginID による社員の識別

補足記事: .NET Framework 4 での属性の変更点

Microsoft .NET Framework 4 からは、.NET Framework 3.5 の DisplayName 属性の代わりに Display 属性の使用を推奨しています。現在も DisplayName は .NET Framework に含まれていますが、Display 属性が使用できる場合は DisplayName は使用しないことをお勧めします。

DisplayName の代わりに Display が推奨される理由は 2 つあります。1 つは、DisplayName 属性ではローカライズがサポートされませんが、Display 属性ではローカライズがサポートされます。

2 つ目は、Display 属性ではあらゆる要素を制御できます。たとえば、フィールドを表示できるさまざまな形式 (プロンプトやヘッダーなど) のテキスト、フィールドをフィルターとして表示するかどうか、またはスキャフォールディングを使用して表示するかどうか (AutoGenerate=false の場合は無効) を制御できます。

したがって、この例で紹介したコードは完全に有効ですが、DisplayName と ScaffoldColumn は Display 属性に置き換えることをお勧めします。ただし、クラス レベルでは、依然として ScaffoldTable 属性と DisplayName 属性を使用する必要があります。

マイクロソフトの他のチームが DataAnnotations 名前空間 (WCF RIA サービス) をサポートしているため、上記の推奨事項に従うことが最適です。これらのプラクティスに従うことで、コードは WCF RIA サービスと問題なく連携できます。

Employee エンティティの ContactID フィールドは、実際には、データベースの Person スキーマに含まれる Contact テーブルの列を参照しています。今回の例では Person スキーマのテーブルを追加していないため、Visual Studio で、ContactID フィールドを直接編集できます。リレーションシップの整合性を保つため、このフィールドの編集は禁止しますが、表示して参照できるようにします。Metadata.cs ファイルを開き、次の MetadataType 属性を適用して Employee クラスを変更します。

[DisplayColumn("LoginID")]
[MetadataType(typeof(EmployeeMetadata))]
public partial class Employee { }

次に、EmployeeMetadata クラスを次のように定義します。

public partial class EmployeeMetadata {
  [Editable(false)]
  public int ContactID { get; set; }
}

Editable 属性は、フィールドを UI から編集できるようにするかどうかを指定します。

次は、EmployeePayHistory エンティティにメタデータを追加して、Rate フィールドを Hourly Rate として表示し、このフィールドの値を通貨で書式設定します。Metadata.cs ファイルに次のクラス定義を追加します。

[MetadataType(typeof(EmployeePayHistoryMetadata))]
public partial class EmployeePayHistory { }
public partial class EmployeePayHistoryMetadata {
  [DisplayFormat(DataFormatString="{0:c}")]
  [Display(Name = "Hourly Rate")]
  public decimal Rate { get; set; }
}

テンプレートをカスタマイズする

Visual Studio プロジェクトには FieldTemplates というフォルダーがあります。このフォルダーには、さまざまなデータ型のフィールドを編集するためのユーザー コントロールが格納されています。既定では、ASP.NET 動的データでは、フィールドと、そのフィールドに関連付けられているデータ型と同じ名前のユーザー コントロールが関連付けられます。たとえば、Boolean.ascx ユーザー コントロールにはブール型フィールドを表示するための UI があり、Boolean_Edit.ascx ユーザー コントロールにはブール型フィールドを編集するための UI があります。

または、UIHint 属性をフィールドに適用して、別のユーザー コントロールが使用されるようにもできます。ここでは、カスタム フィールド テンプレートを追加した Calendar を表示し、Employee エンティティの BirthDate フィールドを編集できるようにします。

Visual Studio で、新しい動的データ フィールド項目をプロジェクトに追加して Date.ascx という名前を付けます。Visual Studio が自動的に Date_Edit.ascx という 2 つ目のファイルを FieldTemplates フォルダーに追加します。まず、Date_Edit.ascx ページの内容を次のマークアップに置き換えます。

<asp:Calendar ID="DateCalendar" runat="server"></asp:Calendar>

次に、図 8 の完全なクラス定義を使用して Date_Edit.ascx.cs ファイルの内容を変更します。

図 8 カスタム Date_EditField クラス

public partial class Date_EditField : System.Web.DynamicData.FieldTemplateUserControl {
  protected void Page_Load(object sender, EventArgs e) {
    DateCalendar.ToolTip = Column.Description;
  }

  protected override void OnDataBinding(EventArgs e) {
    base.OnDataBinding(e);

    if (Mode == DataBoundControlMode.Edit && 
        FieldValue != null) {
      DateTime date = DateTime.MinValue;
      DateTime.TryParse(FieldValue.ToString(), out date);
      DateCalendar.SelectedDate = 
        DateCalendar.VisibleDate = date;
    }
  }
    
  protected override void ExtractValues(
    IOrderedDictionary dictionary) {
    dictionary[Column.Name] = ConvertEditedValue(
      DateCalendar.SelectedDate.ToShortDateString());
  }

  public override Control DataControl {
    get {
      return DateCalendar;
    }
  }
}

OnDataBinding メソッドをオーバーライドして、Calendar コントロールの SelectedDate プロパティと VisibleDate プロパティを FieldValue フィールドの値に設定します。FieldValue フィールドは、FieldTemplateUserControl から継承されるフィールドで、表示されるデータ フィールドの値を表します。また、オーバーライドされる ExtractValues メソッドを変更して、SelectedDate プロパティに対する変更をフィールド名と値のペアの辞書に保存します。ASP.NET 動的データでは、この辞書の値を使用して、基盤となるデータ ソースが更新されます。

次に、BirthDate フィールドに Date.ascx と Date_Edit.ascx をフィールド テンプレートとして使用するよう ASP.NET 動的データに通知する必要があります。それには、2 つの方法のいずれかを実行します。1 つは、次のように UIHint 属性を適用します。

[UIHint("Date")]
public DateTime BirthDate { get; set; }

もう 1 つは、次のように DateType 属性を適用します。

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

DataType 属性では、データ型の名前とユーザー コントロールの名前を対応付けることで、自動的にマッピングが行われます。UIHint 属性では、フィールドの型がユーザー コントロールの名前と一致しない場合に、細かい制御が可能です。ある社員の編集結果を図 9 に示します。

image: Customized Employee Edit Form

図 9 カスタマイズした社員情報の編集フォーム

選択した社員の誕生日を変更して [Update] をクリックすると、新しいデータがデータベースに保存されます。

PageTemplates フォルダーには、各エンティティに適切なビューを表示するページ テンプレートが格納されています。既定では、List.aspx、Edit.aspx、Details.aspx、Insert.aspx、ListDetails.aspx の 5 種類のページ テンプレートがサポートされます。List.aspx ページ テンプレートは、表形式データとしてエンティティを表示する UI を表示します。Details.aspx ページ テンプレートはエンティティの読み取り専用ビューを、Edit.aspx ページ テンプレートはエンティティの表示可能なビューを表示します。Insert.aspx ページ テンプレートは、既定のフィールド値を設定した状態で編集可能ビューを表示します。ListDetails.aspx ページ テンプレートでは、エンティティの一覧を表示できるほか、選択したエンティティの詳細を 1 ページに表示できます。

今回既に説明したとおり、ASP.NET 動的データでは、ルートに定義された {action} パラメーターの値を確認することで、URL 要求が適切なページに自動ルーティングされます。たとえば、ASP.NET 動的データが {action} パラメーターを List と評価したら、List.aspx ページ テンプレートを使用してエンティティの一覧が表示されます。既存のページ テンプレートを変更することも、新しいページ テンプレートをフォルダーに追加することもできます。新しいページ テンプレートを追加する場合は、必ず Global.asax ファイルのルート テーブルに新しいテンプレートを追加してください。

EntityTemplates フォルダーには、読み取り専用モード、編集モード、および挿入モードでエンティティ インスタンスを表示するテンプレートが格納されています。既定では、このフォルダーには、Default.ascx、Default_Edit.ascx、および Default_Insert.ascx の 3 つのテンプレートがあり、それぞれ読み取り専用モード、編集モード、挿入モードでエンティティ インスタンスを表示します。特定のエンティティに適用するテンプレートを作成するには、新しいユーザー コントロールを EntityTemplates フォルダーに追加し、そのコントロールにエンティティ セットの名前を付けます。たとえば、Shifts.ascx という新しいユーザー コントロールを EntityTemplates フォルダーに追加すると、ASP.NET 動的データはこのユーザー コントロールを使用して Shift エンティティ (Shifts エンティティ セット) を読み取り専用モードで表示します。同様に、Shifts_Edit.ascx では Shift エンティティが編集モードで、Shifts_Insert.ascx では挿入モードで表示されます。

エンティティの一覧ごとに、ASP.NET 動的データはエンティティの外部キー フィールド、ブール型フィールド、および列挙型フィールドを使用して、フィルターの一覧を作成します。フィルターは DropDown コントロールとして一覧ページに追加されます (図 10 参照)。

image: Including Data Filters on the Page

図 10 ページへのデータ フィルターの組み込み

ブール型フィルターの場合、DropDownList コントロールには All、True、および False の 3 つの値のみが保持されます。列挙型フィルターの場合、DropDownList コントロールにはすべての列挙値が保持されます。外部キー フィルターの場合は、DropDownList コントロールにすべての一意の外部キー値が保持されます。フィルターは、Filters フォルダー内でユーザー コントロールとして定義されます。既定では、Boolean.ascx、Enumeration.ascx、および ForeignKey.ascx の 3 種類のフィルターしかありません。

まとめ

架空のシナリオでしたが、わずか数分で完全に運用可能な人事 Web サイトを作成できることをご覧いただきました。その後、メタデータとカスタム フィールド テンプレートを追加することで、UI を調整しました。

ASP.NET 動的データには、特別な構成なしで、すばやくサイトを作成して運用できる機能があります。ただし、完全にカスタマイズも可能で、各開発者や組織のニーズに合わせることができます。ASP.NET 動的データは ASP.NET ルーティングをサポートしているため、CRUD 操作用のページ テンプレートを再利用できます。すべての Web アプリケーション プロジェクトに CRUD ページを実装するという面倒な作業を頻繁に実行する必要があり、ストレスを感じている場合は、ASP.NET 動的データを利用すると作業負荷を大幅に削減できます。

James Henry は Microsoft テクノロジのコンサルティングを専門とする企業 BlueVision LLC のフリーのソフトウェア開発者です。著書に『Developing Business Intelligence Solutions Using Information Bridge and Visual Studio .NET』(Blue Vision、2005 年) および『Developing .NET Custom Controls and Designers Using C#』(2003 年、Blue Vision) があります。連絡先は、msdnmag@bluevisionsoftware.com (英語のみ) です。

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