実例で学ぶアプリケーション開発 Ver.2 - サンプルアプリケーションのポイント(1)
皆様、こんにちは!本日は、ちょっと分かり難い構成になっていたというFeedbackを受けまして、元のソリューションサンプルのページを更新しました。再度ご覧戴ければ幸いです。ちょっとスッキリしているかも?
今回はサンプルアプリケーションのアーキテクチャと実装について解説します。開発手順の開発のところは、まだ公開されていませんので、サンプルアプリケーション をダウンロードして、展開した上で、ソリューションを開いてご覧ください。
WCF RIA Servicesの利用
WCF RIA Service を利用する場合に、1 : N となるデータの取得処理では下記パターンが考えられます。本ソリューションサンプルでは下記の実装パターンのうち、MVVM を除いたパターンについて実装を網羅しています。
MVVMパターンについては、別途このソリューションサンプルに追加を検討中です。
※ なお、Tech・Ed 2010 のT6-401では、これとは違うサンプルを使ってMVVMの部分を解説しますので、ご来場予定の方はぜひご参加くださいませ。
WCF RIA Services とは何か?
これについては、これを使った本サンプルソリューションをどのように実装するかは開発ガイドでも順次開設されますが、意外にちゃんとドキュメントの中で説明されていません。
この点は、Tech・Edでも改めて解説しますが、簡単に言うと、下記のような仕組みです。
WCF RIA Services は、N階層アプリケーションを、あたかも2階層のアプリ開発のように行う機能です。前提となっている技術は、ASP.NET、Silverlightであり、通信はWCFの全機能が利用可能です。データアクセス技術は、LINQ及びEntity Frameworkを基盤として、その上でDomainServicesというWeb サービスを、サーバー側に生成し、そのプロキシクラスを、クライアント側(Silverlightプロジェクト)に生成するものです。
ちなみにサーバー側とは、この図でいうと、ECサイト・Webアプリケーションソリューションの
サーバー側である、MSStoreSample.Web というプロジェクトです。ご注意ください。クライアント側(Silverlight)が、MSStoreSampleです(真ん中のは、WPFによるECサイト・店舗用カタログメンテナンスツールです。別途解説しますが、MSStoreSample.CatalogManagerを右クリックして、デバッグ実行→新しいインスタンスで実行すれば使えます。)
WCF RIA Services は、サーバーのオブジェクトモデルを公開しています。その形式は、.NETバイナリー形式、OData (ATOM形式;Read Only)、そしてJSONです。
そして、オブジェクトモデル上のメタデータを使い、妥当性検査をサポートしています。妥当性検査の要求を宣言できます。また、単一プロパティからエンティティ全体までのカスタムロジック追加ができます。 更に、認証と認可のサポートもしています。このあたりも、Tech・Ed 2010 でご紹介します。
サーバー側のサービスである、DomainServiceには、基本的なCRUD(新規作成・読込・更新・削除)のデータ処理が自動的に生成されます。
これを作るには、
・Entity Framework 等を使いDataSourceから生成した .edmxを使いDomainService を定義
する必要があります。
ここでビルドすることにより、
・クライアント側のSilverlight プロジェクトにDomainService ソースがコピーされプロキシクラスを生成(DomainContext)
します。
サーバー側でDomainServiceをBuildすると、クライアント側に、プロキシクラスが自動生成されます。これが DomainContext です。このDomainContext を使ってCRUD処理を行うことにより、Silverlightの非同期の開発を、同期のように取り扱うことができるのです。これが生産性、効率性、に寄与することは言うまでもありません。
そして、この DomainContextを保持するコントロールである DomainDataSourceを利用したVisual 開発が可能です (Visual Studio 2010との連携による)。
Domain Data Source による実装の注意点
① イベントハンドラによる1 : N リレーションの読み込み
それでは、ソリューションサンプルのアプリケーションを開いて、商品一覧ページ( Home.xaml )を開いてください。商品一覧ページでは、イベントハンドラによる1 : N リレーションの読み込みを、Domain Data Source を利用して実現しています。
マスター(1側)に相当する Categories をデータソース ウィンドウから、Home.xaml のcategoriesListBox コントロールにドラッグ&ドロップを行い、下記の様に、DoaminDataSourceコントロールと、categoriesListBox コントロールへのデータバインドを生成します。 サンプル: \MSStoreSample\MSStoreSample\Views\Home.xaml
明細(N側)に相当する DefaultColorProducts についてはcategoriesListBox コントロールの SelectionChanged イベントのイベントハンドラ内で実装を行います。
DomainDataSource コントロールの保持するDomainContext を利用して、(N側)に相当する DefaultColorProductsのリストをドメインサービスから取得します。
/// <summary>
/// カテゴリの一覧で、カテゴリの選択時の処理を実装します。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void categoriesList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Categories selectedCategory = (Categories)categoriesList.SelectedItem;
MSStoreSampleDomainContext context = (MSStoreSampleDomainContext)categoriesDomainDataSource.DomainContext;
context.DefaultColorProducts.Clear();
productListBox.ItemsSource = context.DefaultColorProducts;
context.Load(context.GetDefaultColorProducutsByCategoryIdQuery(selectedCategory.CategoryId));
}
6行目以降を覧ください。DomainDataSource はコントロールとして使えるので、これをDrag & Dropで利用すると、非常に便利です。
しかし、Drag & Dropすると、バインドしたコントロール(ここでは productListBox ) の SelectionChaged イベントが、その時点で発生してしまいます。すなわち、productListBox_SelectionChanged メソッドによる、商品詳細画面(Product.xaml)への画面遷移処理が実行されてされてしまうのです。
そこで、あえて DomainContext に対してデータ取得処理を実装しています。
② Include による1 : N リレーションの読み込み(関連エンティティの読み込み)
これまた、かなり**重要な部分**です。注文履歴ページ(OrderHistory.xaml)では 、1 : N リレーションの一括での読み込みをDomain Data Source を利用して実現しています。
まずWebアプリケーションのDomainService、及びEntity Meta Data で下記の実装を行い、DomainService のクエリメソッドで関連エンティティを読み込み、これをクライアントに送信するように実装します。
・ Entity Meta Data の明細を参照する OrderItems プロパティに Include 属性を追加する。
・ DomainService のクエリメソッド内のクエリ式で Includeメソッドを追加し、関連エンティティをデータベースから読み込む。
まずは、明細を参照するOrderItems プロパティにInclude 属性を追加します。
サンプル:\MSStoreSample\MSStoreSample.Web\Services\MSStoreSampleDomainService.metadata.cs
// The MetadataTypeAttribute identifies OrdersMetadata as the class
// that carries additional metadata for the Orders class.
[MetadataTypeAttribute(typeof(Orders.OrdersMetadata))]
public partial class Orders
{
// This class allows you to attach custom attributes to properties
// of the Orders class.
//
// For example, the following marks the Xyz property as a
// required property and specifies the format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz { get; set; }
internal sealed class OrdersMetadata
{
…
[Include]
public EntityCollection<OrderItems> OrderItems { get; set; }
…
}
}
次に DomainService のクエリメソッド内のクエリ式でIncludeメソッドを追加し、関連エンティティをデータベースから読み込みます。
サンプル: \MSStoreSample\MSStoreSample.Web\Services\MSStoreSampleDomainService.cs
[Query]
public IQueryable<Orders> GetOrdersByUserId(string userId)
{
return this.ObjectContext.Orders
.Include("OrderItems")
.Include("OrderItems.Products")
.Include("OrderItems.ProductColors")
.Where(order => order.Customers.UserId == userId);
}
次に、Silverlight クライアント側に下記の操作を行い、データ取得とバインディングを実装します。
マスター(1側)に相当する Orders をデータソース ウィンドウから OrderHistory.xaml にドラッグ&ドロップを行い、下記の様に、DoaminDataSourceコントロールとDataGridコントロール、およびそのデータバインドを生成します。
明細(N側)に相当する OrderItems については、データソース ウィンドウ Orders を展開し、そのツリー内の OrderItems をOrderHistory.xaml にドラッグ&ドロップを行います。
分離コードによる実装
以上に対して、単一のエンティティを取得する場合には、下記のように DoaminContext のLoad メソッドの引数にコールバックメソッドを引き渡し、非同期処理の完了時に取得してきたエンティティのハンドルを実装します。
MSStoreSampleDomainContext context;
/// <summary>
/// ページ遷移が完了した際のイベントを実装します。
/// NavigationContext のクエリ文字列に格納された商品 ID を元に商品情報をドメインサービスから取得し表示します。
/// </summary>
/// <param name="e"></param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// NavigationContext.QueryString に格納された商品IDを利用するためここでは DomainContext をコードビハインドで操作しています。
…
context = new MSStoreSampleDomainContext();
context.Load(context.GetProductsByIdQuery(productId), ProductLoadOperationCompleted, null);
}
/// <summary>
/// 非同期によるサービスからの商品情報取得完了のコールバック処理を実装します。
/// ページ上に表示するデータは商品情報ではなく、商品情報を元に作成されたバスケットアイテムの情報を表示します。
/// バスケットへはここで作成されたバスケットアイテムを追加します。
/// </summary>
/// <param name="loadOperation"></param>
private void ProductLoadOperationCompleted(LoadOperation<Products> loadOperation)
{
Products product = loadOperation.Entities.SingleOrDefault();
…
context.Load(context.GetProductColorsByProductIdQuery(product.ProductId), ProductColorsLoadOperationCompleted, product);
context.Load(context.GetProductSizesByProductIdQuery(product.ProductId), ProductSizesLoadOperationCompleted, null);
context.Load(context.GetProductStylesByProductIdQuery(product.ProductId), ProductStylesLoadOperationCompleted, null);
}
以上です。かなり内容的には核心に迫るものになっています。ちょっと先取りとなります(開発手順が順次公開されます)ので、それと併せて、この記事をもう一度合わせて読んでみてください。
ではまた。
鈴木 章太郎