メタデータクラスによる表示データの制御と値の検証
メタデータクラスはエンティティクラスの対となるクラスで、エンティティデータの属性情報を定義するクラスです。対になって1つのクラスを表すため、「バディクラス」と呼ばれることもあります。
Visual Studioのウィザードからドメインサービスを作成すると、エンティティクラスのインナークラスとして、メタデータクラスが作成されます。
WCF RIA Servicesでは、データの検証情報やUIに表示させるための表示名、表示順序などのメタ情報をメタデータクラス側に定義することで、エンティティクラス側がウィザードなどで再生成されても、ユーザが定義したメタ情報が失われないようになっています。
リスト1は、お小遣い明細クラスに対し、メタデータクラスを通じてデータの表示方法や検証情報を追加した例です。データアノテーションを利用した値の検証については、過去記事『Silverlight 3で強化されたスタイルと値の検証』(CodeZine)を参照してください。
[MetadataTypeAttribute(typeof(お小遣い明細.お小遣い明細Metadata))] public partial class お小遣い明細 { internal sealed class お小遣い明細Metadata { private お小遣い明細Metadata() { } [Display(Name = "日付", Order = 0)] public DateTime 日付 { get; set; } [Display(Order = 1)] [Range(0, 20, ErrorMessage="{0}は{1}から{2}の間で入力してください。")] public int 連番 { get; set; } // ... 略 ... [Include] public byte[] TimeStamp { get; set; } [Exclude] public Nullable<DateTime> 最終更新日 { get; set; } } }
ここで注目するのは、お小遣い明細クラスに指定された属性情報です。MetadataTypeAttribute属性は、指定されたクラスに対するメタデータクラスを指定するための属性です。
お小遣い明細クラスの本体は「お小遣いEntites.Designer.cs」にパーシャルクラスとして定義されているので、ここではメタデータクラスの指定のみを行っています。
前編でデータグリッドに表示するサンプルを作成した後なので、Silverlight側のUIには特に変化はありません。しかし、Display属性を定義することで、UIを定義する際に、カラムの表示名やカラムの表示順といった情報をVisual Studioのデザイナーに伝えることができます。
また、最終更新日プロパティはサーバ側で自動的に割り振るデータであるため、クライアント側には追加されないように、Exclude属性を追加しています。逆に、クライアント側に追加する場合に指定する属性は、Include属性です。Include属性はデフォルトで有効になるため、明示的に定義する必要はありません。
この状態で前編のサンプルを起動し、連番に「21」といった範囲外の数字を入力すると、図4のように値の検証が行われ、正しい値を入力するか、入力をキャンセルするまでデータを確定できません。
では、自動生成されたコード側を確認してみましょう。Silverlightプロジェクトの「Generated_Code」フォルダの「RIAServicesSample.Web.g.cs」から、お小遣い明細クラスを探してください。リスト1で定義した通り、自動生成されたコードでは、お小遣い明細Metadataクラスの情報が、お小遣い明細クラスのプロパティに定義されていることを確認できます(リスト2)。
/// <summary> /// Gets or sets the '連番' value. /// </summary> [DataMember()] [Display(Name="連番", Order=1)] [Range(0, 20, ErrorMessage="{0}は{1}から{2}の間で入力してください。")] public int 連番 { get { return this._連番; } set { if ((this._連番 != value)) { this.On連番Changing(value); this.RaiseDataMemberChanging("連番"); this.ValidateProperty("連番", value); this._連番 = value; this.RaiseDataMemberChanged("連番"); this.On連番Changed(); } } }
このようにメタデータクラスを利用することで、データの検証情報とデザイン時の表示内容を、サーバ側で定義できます。
共有コード
ここまでで、エンティティクラスにはデータモデルの定義を、メタデータクラスにはデータの属性情報を定義してきました。では、データの振る舞いを追加するには、どんな方法があるでしょうか。1つは後述するドメインサービスに定義する方法で、もう1つは共有コードに記述する方法です。
共有コードは、サーバとクライアントでコードを共有するしくみです。例えば、新しくお小遣い帳のレコードを作成して初期化するコードは、サーバ側とクライアント側で同じコードを利用するはずです。このようなコードを記述したい場合、「*.share.cs」といった命名規則のクラスをソリューションに追加します。
ここでは、お小遣い帳クラスのパーシャルクラスとして「お小遣い明細.share.cs」という名前の共有コードを作成しました。共有コードを配置する場所はWCF RIA Serviceリンクが設定されたプロジェクトであれば、どこであっても問題ありません。重要なのは、拡張子が「*.share.cs」であることです。
ドメインサービスに定義されたメソッドの違いは、ドメインサービスのメソッドはあくまでサーバにあり、WCFを通じて呼び出すのに対し、共有コードのメソッドはクライアントにコピーされ、クライアントで動作します。
今回追加した共有コードをリスト3に示します。この例では、単純にお小遣い明細の日付プロパティに当日の日付を設定しているだけです。
partial class お小遣い明細 { public static お小遣い明細 Create() { return new お小遣い明細 { 日付 = DateTime.Today }; } }
このコードをコンパイルすると、Silverlightプロジェクトの「Generate_Code」フォルダに、サーバ側のプロジェクトで定義した「お小遣い明細.shared.cs」と同じファイルが生成されるのを確認できます。このように共有コードを利用することで、エンティティのパーシャルクラスのコードやユーティリティクラスのコードを、サーバとクライアントで共有できます(図5)。
WCF RIA Servicesを利用していると、サーバ側に登録されたサービスであっても、まるでローカルにあるクラスのメソッドのように利用できます。当たり前のことですが、サーバ側に登録されたサービスを利用するには、データセンターなどの遠隔地にあるサービスを呼び出すことになり、パフォーマンス面では大きなデメリットになります。
この手の問題は、開発時には、サービスを提供するサーバと、Silverlightが動作するサーバが同じであるため問題になりませんが、サービス提供時やパフォーマンステスト時など、プロジェクト後半になって問題が表面化することが多いです。
一概にこうすれば良いという解決策はありませんが、特にサーバリソースを必要としないユーティリティ的なロジックは、共有コードにしてクライアント側にコードを同期させるというのも、1つの方法だと筆者は考えます。