非モデルバインドの問題点
従来のデータソースコントロールとデータバインドコントロールの組み合わせには、単純な処理ならコードビハインドが一切不要になるという利点がありました。その反面、少し凝ったことをやろうとすると、ItemInserting、ItemInsertedなどの処理前後の各種イベントを駆使して処理を記述する必要があります(リスト1、2)。
<asp:FormView ID="ReservationsFormView" runat="server" DataSourceID="ReservationsObjectDataSource" DefaultMode="Insert" ItemType="MRRS.Entity.Reservation" OnItemInserting="ReservationsFormView_ItemInserting" OnItemInserted="ReservationsFormView_ItemInserted">
protected void ReservationsFormView_ItemInserting(object sender, FormViewInsertEventArgs e) { // データバインドできないため、自前で値を設定する e.Values.Add("MeetingRoomId", MeetingRoomDropDownList.SelectedValue); e.Values["ReserveDateFrom"] = DateTimeUtilities.ConvertDateTime(ReserveDateFromTextBox.Text, ReserveDateFromTimeTextBox.Text); e.Values["ReserveDateTo"] = DateTimeUtilities.ConvertDateTime(ReserveDateToTextBox.Text, ReserveDateToTimeTextBox.Text); } protected void ReservationsFormView_ItemInserted(object sender, FormViewInsertedEventArgs e) { var ex = e.Exception; if (ex != null) { if (ex.InnerException is ReservationLogic.OverlapReservationException) { this.ShowErrorMessage("予約期間が重なる予約がすでに登録されています。"); // 入力値を維持 e.KeepInInsertMode = true; // 例外を処理済みとマーク e.ExceptionHandled = true; return; } } // 予約参照画面に戻る Response.Redirect("~/Reservations.aspx"); }
この方法の大きな問題点は、本来は「追加」といったような1つのアクションに関連する処理が、各イベントに分散してしまうことです(図1)。サンプルの例でいえば、①追加前イベントハンドラーで入力値を取得、設定し、②ビジネスロジックの追加処理を呼び出した後、③追加後イベントハンドラーでエラーの有無を確認、というようになっています。このようにイベントの発生順を覚えていないと処理される順番も分かりませんし、それぞれのイベントを組み合わせてどんなことをしようとしているかの概観を理解するだけでも一苦労です。
もう一つ、ビジネスロジック上のエラーを例外を使って表す以外に方法がないことも問題です。本来業務エラーは戻り値などを使って扱いたいところですが、非モデルバインドではそれができません。また処理フロー制御に例外を用いるのは、原則的には避けるべきです。
モデルバインドでの問題解決
そこで、ASP.NET 4.5のWebフォームで新たに導入されたモデルバインドを用いることで、この問題を解決できます。モデルバインドでは、検索、追加、更新、削除といった、いわゆるCRUD処理それぞれに対応するメソッドを、コードビハインドに記述します。そのため、1つのアクションに対応する処理が分散することがありません(図2)。また、もちろん業務エラーも戻り値で表すことができるようになります。サンプルの例でいえば、①aspxファイルに指定した更新処理メソッドを直接呼出し、入力値の取得、設定を行った後、②ビジネスロジックの更新処理メソッドを呼び出します。そして③戻り値を判断して、以後の処理の分岐を行います。
ただ、当然データソースコントロールを使えば不要だったコードビハインドのコードが必要となるため、その使用には注意が必要です。何でもかんでもモデルバインドを使ってしまうと、その分コード量が増えてしまいバグを埋め込む可能性が高まります。
モデルバインドの使用方法
それでは、モデルバインドの具体的な使用方法を学んでいきましょう。前述のとおり、モデルバインドではCRUDそれぞれに対応するメソッドをコードビハインドに定義します。そして、それらのメソッドをaspxファイルでデータバインドコントロールに関連付けるというのが基本的な考え方です。サンプルの予約情報詳細画面(ReservationsDetail.aspx、aspx.cs)を例に、順に説明していきます。
検索処理メソッド定義
まず、予約情報詳細画面では予約情報検索画面で選択された予約情報のIDを元に、編集対象データをデータベースから取得する必要があります。この検索処理メソッドは次のようになります(リスト3)。
// (1) 検索処理メソッド定義 public Reservation SelectReservation([QueryString]int reservationId) { // (2) 削除後に検索処理が呼ばれるため、同じデータを返すようSessionに入れておく if (Session["reservation"] != null) { return Session["reservation"] as Reservation; } // (3) ビジネスロジックの検索処理を呼び出す var logic = new ReservationLogic(); var reservation = logic.Find(reservationId); Session.Add("reservation", reservation); return reservation; }