複雑な画面のモデル検証
最後に、少し複雑な画面でモデル検証を行う方法を簡単に紹介しましょう。例には予約情報編集画面を用います。ここで、「複雑」とは、①DBの項目と画面の項目が1対1ではない、②検証属性で処理できない独自のチェックが必要、といったことを表します。
DBの項目と画面の項目が1対1ではない場合の対応
まず、DBの項目と画面の項目が1対1ではない場合についてです。このケースへはDBと画面は別物として考え、モデルバインド、検証用にViewModel(ビューモデル)を用意します。これは、主にMVC系のパターンを用いたPDS(Presentaion Domain Separation、プレゼンテーションとドメインの分離(注1))の実現方法と同じ考え方です。
例では、予約情報編集画面のために、App_Codeフォルダに新たにReservationViewModel.csというクラスを用意しています(リスト6)。このクラスには、画面の項目と1対1になるようなプロパティを定義し、それぞれに検証属性をつけています。具体的には、予約開始/終了日時が画面では日付と時刻に分かれていますが、DBでは1つの項目です。そこで、日付、時刻それぞれのプロパティを用意しています。
コードをユーザーインターフェース(プレゼンテーション)とその他の機能(ドメイン)の責務で分割することです。プレゼンテーション・プラットフォームの都合が関係ある処理とそうでない処理を分割することで、理解のしやすさを向上させたり、メンテナンスを容易にすることが可能です。
詳しくは、「Martin Fowler's Bliki in Japanese-プレゼンテーションとドメインの分離」を参照してください。
public class ReservationsDetailViewModel { public int ReservationId { get; set; } public int LocationId { get; set; } [Required(ErrorMessage = "会議室を選択してください。")] public string MeetingRoomId { get; set; } [Required(ErrorMessage = "予約開始日を入力してください。")] public string DateOfReserveDateFrom { get; set; } [Required(ErrorMessage = "予約開始時刻を入力してください。")] public string TimeOfReserveDateFrom { get; set; } [Required(ErrorMessage = "予約開始日を入力してください。")] public string DateOfReserveDateTo { get; set; } [Required(ErrorMessage = "予約開始時刻を入力してください。")] public string TimeOfReserveDateTo { get; set; } public string Purpose { get; set; } public string Remarks { get; set; } public byte[] Version { get; set; } …(中略)… }
ただし、このやり方はビジネスロジックを呼び出す際、ViewModelとDBから取り出したオブジェクトの間で、値の詰め替えがどうしても発生します(リスト7)。項目数が多い場合は、「AutoMapper」などのサポートライブラリの使用も検討しましょう。
public ReservationsDetailViewModel SelectReservation([QueryString]int reservationId) { Reservation reservation; // 削除後に検索処理が呼ばれるため、同じデータを返すようSessionに入れておく if (Session["reservation"] != null) { reservation = Session["reservation"] as Reservation; } else { var logic = new ReservationLogic(); reservation = logic.Find(reservationId); Session["reservation"] = reservation; } return new ReservationsDetailViewModel { ReservationId = reservation.ReservationId, LocationId = reservation.MeetingRoom.LocationId, MeetingRoomId = reservation.MeetingRoomId.ToString(), DateOfReserveDateFrom = reservation.ReserveDateFrom.ToString("yyyy/MM/dd"), TimeOfReserveDateFrom = reservation.ReserveDateFrom.ToString("HH:mm"), DateOfReserveDateTo = reservation.ReserveDateTo.ToString("yyyy/MM/dd"), TimeOfReserveDateTo = reservation.ReserveDateTo.ToString("HH:mm"), Purpose = reservation.Purpose, Remarks = reservation.Remarks, Version = reservation.Version, }; }
独自のチェック実行方法
次に、独自のチェックを行う方法を説明します。これは以下の方法で実現ができます。
- バインド先モデルにIValidatableObjectインターフェースを実装する
- IValidatableObject.Validateを定義する
予約情報編集画面では、予約開始日/時刻、予約終了日/時刻の形式チェック並びに前後関係チェックをValidateメソッドで行っています(リスト8)。
public class ReservationsDetailViewModel : IValidatableObject { …(中略)… public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { if (!String.IsNullOrEmpty(DateOfReserveDateFrom)) { if (!DateTimeUtilities.IsDate(DateOfReserveDateFrom)) { yield return new ValidationResult("予約開始日には日付(yyyy/MM/dd形式)を入力してください。"); } } if (!String.IsNullOrEmpty(TimeOfReserveDateFrom)) { if (!DateTimeUtilities.IsTime(TimeOfReserveDateFrom)) { yield return new ValidationResult("予約開始時刻には時刻(HH:mm形式)を入力してください。"); } } …(中略)… } }
なお、Validateメソッドでは戻り値のValidationResult型オブジェクトをyield returnで返すことで、ValidationSummaryコントロールにそのメッセージを表示することができます。ValidationResult型には、コンストラクタ引数にエラーメッセージを指定します。
以上で、複雑なモデル検証の実装は終わりです。紙面の都合で実行結果は掲載しませんが、各自でサンプルを動かして動作を確認してみてください。
また、このままの状態ではクライアント検証は動作しません。動作させるにはどのように修正すればよいか、挑戦してみるのもよいでしょう。
まとめ
今回はモデルバインドを使う際には欠かせない、モデル検証の概念、実装方法について学びました。ポイントをまとめると、次のようになります。
-
モデルバインドは検証処理をViewからModel側に集約する
- 検証ロジックをUIから分離できる
- 複数画面で同じ検証ロジックを使いまわせる
-
モデル検証を行うには、次の手順が必要
- モデルのプロパティにSystem.ComponentModel.DataAnnotations名前空間の検証属性をつける
- データバインドコントロールでモデル検証を有効にする
- Page.ModelStateプロパティのIsValidプロパティで検証結果を判定する
-
単にモデル検証を使うだけではクライアントサイド検証は行われない
- クライアントサイド検証を行うには、Dynamic DataのDynamicコントロールやDynamicFieldを活用する
-
複雑な画面でモデル検証を行うには、少し手間が必要
-
画面とDBの項目が1対1でない
- 画面専用のViewModelを用意し、PDS(Presentation Domain Separation)を図る
-
独自の検証処理が必要
- IValidatableObjectインターフェースを実装し、Validateメソッドを定義する
-
画面とDBの項目が1対1でない
今回までの流れで、主要なASP.NET 4.5の新機能は紹介できたのではないかと考えています。次回は残りの新機能について、ざっと紹介する予定です。お楽しみに。