その他のサービスメソッドの呼び出し
WCF RIA Servicesでは、検索、更新、追加、削除といったCRUDを行うメソッド以外にも、ドメインサービスにサービスを登録して、クライアント側から呼び出すことができます。
リスト6では、「お小遣いService.cs」にコードを追加して、対象月のお小遣い明細の合計金額を算出するサービスを公開しています。
[Invoke] public int? Get月の合計金額(DateTime 対象月) { var 年 = 対象月.Year; var 月 = 対象月.Month; var 月初 = new DateTime(年, 月, 1); var 月末 = new DateTime(年, 月, DateTime.DaysInMonth(年, 月)); return this.ObjectContext.お小遣い明細 .Where(明細 => 月初 <= 明細.日付 && 明細.日付 <= 月末) .Sum(明細 => 明細.金額); }
コード自体は単にEDMからLINQを使って対象のデータを検索してくるものなので、説明は不要でしょう。ドメインサービスでクライアントに公開したいメソッドは、Invoke属性を指定します。
Query属性がついたコードとの違いは、WCF RIA Servicesによってデータのトラッキングが行われるかどうかです。リスト7に、このメソッドを呼び出すSilverlight側のコードを示します。
private void 今月の合計金額_Click(object sender, RoutedEventArgs e) { var context = (お小遣いContext)お小遣い明細DomainDataSource.DomainContext; context.Get月の合計金額( DateTime.Today, result=> MessageBox.Show(string.Format("今月の利用金額:{0}円", result.Value ?? 0)), null); }
リスト6のコードを記述してコンパイルすると、ドメインコンテキストにサービスの呼び出しコードが追加されるので、先ほど登録したメソッドを呼び出しています。サービス自体は非同期で実行されるため、サービスが終了した際の動作として、メッセージボックスに合計金額を表示させるコードを追加しています。
更新時のエラー処理
Silverlightでの更新処理は、複数のレコードに対する更新が非同期に行われます。そのため、更新要求が成功したかをSilverlight側で判断するには、更新終了時のイベントハンドラーで、送信した変更セットからエラーの有無を確認する必要があります。
DomainDataSourceコントロールで更新終了後のイベントは、SubmittedChangesイベントです。リスト8は、更新終了時に更新でのエラーの有無を調べてメッセージを表示する例です。
private void お小遣い明細DomainDataSource_SubmittedChanges(object sender, SubmittedChangesEventArgs e) { if (e.HasError) { e.MarkErrorAsHandled(); MessageBox.Show("更新エラーが発生しました。"); } else { MessageBox.Show("更新が終了しました。"); } }
ドメインサービスの更新処理で例外が発生すると、SubmittedChangesEventArgsのHasErrorプロパティにtrueがセットされます。さらに詳しい例外情報を取得したい場合は、EntitiesInErrorプロパティから例外が発生したエンティティの一覧を取得して、原因を調べることができます。
サーバ側で発生した例外がここで回復(ハンドル)できる場合は、MarkErrorAsHandledメソッドを呼び出し、これ以上例外が伝播しないようにマークする必要があります。例外を補足しない場合は、Application_UnhandledExceptionイベント等で例外を処理します。
TimeStamp列を利用した同時実効性制御
データベースにSQLServerを利用している場合は、TimeStamp列を利用した楽観的同時実行性制御を行うことができます。この設定は、TimeStamp列に対して、Entity FrameworkのデータモデルでFiexdを設定するだけです(図8)。
この状態で、複数のブラウザーから同じレコードを更新して保存を行うと、HasErrorプロパティでデータの競合を検知できます。データの競合が発生した場合にSilverlight側で競合を解決する例を、リスト9に示します。
if (e.HasError) { // ErrorからDomainOperationExceptionを取り出し、 var domainOpException = e.Error as DomainOperationException; if (domainOpException != null) { // ステータスがConflicts(競合)のものをチェックする。 if (domainOpException.Status == OperationErrorStatus.Conflicts) { // エラーが発生したエンティティ一覧から、競合が発生しているレコードを特定し、 foreach (var err in e.EntitiesInError.Where(err => err.EntityConflict != null)) { // ローカル側の更新を優先する場合はResoleve(解決)を行う err.EntityConflict.Resolve(); } } MessageBox.Show("更新中にデータの競合が発生しました。更新を優先する場合は、再度保存ボタンをクリックしてください。"); } e.MarkErrorAsHandled(); }
サーバ側で競合が発生した場合は、例外のステータスにConflicts(競合)が設定されます。
データの競合が発生し、ローカル側の更新を優先させたい場合は、EntitiesInErrorプロパティから競合のあったレコードの一覧を取得し、Resolveメソッドを呼び出してデータの競合を解決します。この状態で再度SubmitChangeメソッドを呼び出すと、ローカルのデータを優先してサーバ側を更新できます。
Silverlightでユーザ認証や承認機能を実装する場合、ASP.NETの認証や承認機能をそのまま利用できます。WCF RIA Servicesでは、ASP.NETの認証情報を元に、ドメインサービスに登録されたメソッドを、認証の状態や承認されたロールによって許可を与えることができます。
例えば、お小遣い帳の検索はログイン指定は不要ですが、更新はログインが必要、というような場合です。この場合、認証が必要なドメインサービスのメソッドに、RequiresAuthentication属性を追加します。
他にも、RequiresRole属性を利用することで、ログイン中のユーザが特定のロールを持っている場合のみ、メソッドの起動を許可するといった制御も可能です。
まとめ
WCF RIA Servicesはまだ出始めで、かゆいところに手が届かない部分もありますが、今の段階でも十分にビジネスに利用できる状態だと筆者は思います。
現在のWCF RIA ServicesはSilverlight専用ですが、今後の機能拡張では、WPFやWindowsFormやWebForm、MVCとさまざまな環境で適用できるようになっていくでしょう。これからのn階層アプリケーションの構築では、標準的な位置になってくると予想されます。ぜひこの機会にWCF RIA Servicesに触れてみてください。