ViewData を使用し、ViewModel クラスを実装する
提供元: Microsoft
これは、ASP.NET MVC 1 を使用して小規模で完全な Web アプリケーションを構築する方法を説明する無料の "NerdDinner" アプリケーション チュートリアルの手順 6 です。
手順 6 では、より豊富なフォーム編集シナリオのサポートを有効にする方法を示し、コントローラーからビューにデータを渡すために使用できる 2 つの方法 (ViewData と ViewModel) についても説明します。
ASP.NET MVC 3 を使用している場合は、MVC 3 の概要または MVC Music Store に関するチュートリアルに従うことをお勧めします。
NerdDinner 手順 6: ViewData と ViewModel
さまざまなフォーム投稿シナリオについて説明し、作成、更新、削除 (CRUD) のサポートを実装する方法について説明しました。 DinnersController の実装をさらに進め、より豊富なフォーム編集シナリオのサポートを有効にします。 ここでは、コントローラーからビューにデータを渡すために使用できる 2 つの方法 (ViewData と ViewModel) について説明します。
コントローラーからビュー テンプレートにデータを渡す
MVC パターンの定義特性の 1 つは、アプリケーションのさまざまなコンポーネント間で適用するのに役立つ厳密な "懸念事項の分離" です。 モデル、コントローラー、ビューはそれぞれ、適切に定義された役割と責任を持ち、明確に定義された方法で相互に通信を行います。 これは、テスト容易性とコードの再利用を促進するのに役立ちます。
Controller クラスは、HTML 応答をクライアントにレンダリングする場合、応答をレンダリングするために必要なすべてのデータをビュー テンプレートに明示的に渡す役割を担います。 ビュー テンプレートでは、データ取得やアプリケーション ロジックを実行しないでください。代わりに、コントローラーによって渡されるモデルまたはデータから駆動されるレンダリング コードのみに制限する必要があります。
現在、DinnersController クラスによってビュー テンプレートに渡されるモデル データは単純で簡単です。Index() の場合は Dinner オブジェクトの一覧、Details()、Edit()、Create()、Delete() の場合は 1 つの Dinner オブジェクトです。 アプリケーションに UI 機能を追加するにつれて、多くの場合、ビュー テンプレート内で HTML 応答をレンダリングするために、このデータ以上のものを渡す必要が出てきます。 たとえば、編集ビューと作成ビュー内の "Country" フィールドを HTML テキスト ボックスからドロップダウン リストに変更できます。 ビュー テンプレートで国と地域の名前のドロップダウン リストをハードコーディングするのではなく、動的に設定される、サポートされている国と地域の一覧から生成することもできます。 Dinner オブジェクト "と" サポートされている国と地域の一覧の両方をコントローラーからビュー テンプレートに渡す方法が必要です。
これを実現する 2 つの方法を見てみましょう。
ViewData ディクショナリの使用
Controller 基底クラスは、コントローラーからビューに追加のデータ項目を渡すために使用できる "ViewData" ディクショナリ プロパティを公開します。
たとえば、編集ビュー内の "Country" テキスト ボックスを HTML テキスト ボックスからドロップダウン リストに変更するシナリオをサポートするために、Edit() アクション メソッドを更新して、"Country" ドロップダウン リストのモデルとして使用できる SelectList オブジェクトを (Dinner オブジェクトに加えて) 渡すことができます。
//
// GET: /Dinners/Edit/5
[Authorize]
public ActionResult Edit(int id) {
Dinner dinner = dinnerRepository.GetDinner(id);
ViewData["Countries"] = new SelectList(PhoneValidator.AllCountries, dinner.Country);
return View(dinner);
}
上記の SelectList のコンストラクターは、ドロップダウン リストに設定される国と地域の一覧と、現在選択されている値を受け入れます。
その後、前に使用した Html.TextBox() ヘルパー メソッドの代わりに Html.DropDownList() ヘルパー メソッドを使用するように、Edit.aspx ビュー テンプレートを更新できます。
<%= Html.DropDownList("Country", ViewData["Countries"] as SelectList) %>
上記の Html.DropDownList() ヘルパー メソッドは、2 つのパラメーターを受け取ります。 1 つ目は、出力する HTML フォーム要素の名前です。 2 つ目は、ViewData ディクショナリを介して渡した "SelectList" モデルです。 C# の "as" キーワードを使用して、ディクショナリ内の型を SelectList としてキャストしています。
アプリケーションを実行し、ブラウザー内で /Dinners/Edit/1 URL にアクセスすると、編集 UI が更新され、テキスト ボックスではなく国と地域のドロップダウン リストが表示されることがわかります。
また、HTTP-POST Edit メソッドから編集ビュー テンプレートをレンダリングするため (エラーが発生した場合のシナリオでは)、ビュー テンプレートがエラー シナリオでレンダリングされるときに SelectList を ViewData に追加するように、このメソッドも更新することもできます。
//
// POST: /Dinners/Edit/5
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection collection) {
Dinner dinner = dinnerRepository.GetDinner(id);
try {
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
catch {
ModelState.AddModelErrors(dinner.GetRuleViolations());
ViewData["countries"] = new SelectList(PhoneValidator.AllCountries, dinner.Country);
return View(dinner);
}
}
DinnersController 編集シナリオで DropDownList がサポートされるようになりました。
ViewModel パターンの使用
ViewData ディクショナリのアプローチには、実装が非常に高速で簡単であるという利点があります。 ただし、入力ミスがコンパイル時にキャッチされないエラーにつながる可能性があるため、文字列ベースのディクショナリを使用するのが好きではない開発者もいます。 型指定されていない ViewData ディクショナリでは、ビュー テンプレートで C# のような厳密に型指定された言語を使用する場合は、"as" 演算子またはキャストを使用する必要もあります。
使用できる別のアプローチは、多くの場合、"ViewModel" パターンと呼ばれるものです。 このパターンを使用する場合は、特定のビュー シナリオ用に最適化され、ビュー テンプレートで必要な動的な値またはコンテンツのプロパティを公開する、厳密に型指定されたクラスを作成します。 コントローラー クラスは、これらのビュー最適化クラスを設定し、使用するビュー テンプレートに渡すことができます。 これにより、ビュー テンプレート内のタイプ セーフ、コンパイル時チェック、エディターの Intellisense が有効になります。
たとえば、ディナー フォームの編集シナリオを有効にするには、次のような "DinnerFormViewModel" クラスを作成します。このクラスでは、厳密に型指定された 2 つのプロパティ (Dinner オブジェクトと、"Countries" ドロップダウン リストを設定するために必要な SelectList モデル) が公開されます。
public class DinnerFormViewModel {
// Properties
public Dinner Dinner { get; private set; }
public SelectList Countries { get; private set; }
// Constructor
public DinnerFormViewModel(Dinner dinner) {
Dinner = dinner;
Countries = new SelectList(PhoneValidator.AllCountries, dinner.Country);
}
}
その後、Edit() アクション メソッドを更新して、リポジトリから取得した Dinner オブジェクトを使用して DinnerFormViewModel を作成し、それをビュー テンプレートに渡すことができます。
//
// GET: /Dinners/Edit/5
[Authorize]
public ActionResult Edit(int id) {
Dinner dinner = dinnerRepository.GetDinner(id);
return View(new DinnerFormViewModel(dinner));
}
次に、edit.aspx ページの上部にある "inherits" 属性を次のように変更することで、"Dinner" オブジェクトではなく "DinnerFormViewModel" が必要になるようにビュー テンプレートを更新します。
Inherits="System.Web.Mvc.ViewPage<NerdDinner.Controllers.DinnerFormViewModel>
これを行うと、ビュー テンプレート内の "Model" プロパティの Intellisense が更新され、渡す DinnerFormViewModel 型のオブジェクト モデルが反映されます。
その後、ビュー コードを更新して動作させることができます。 以下の作成する入力要素の名前を変更しない方法 (フォーム要素は引き続き "Title"、"Country") に注意してください。DinnerFormViewModel クラスを使用して値を取得するように HTML ヘルパー メソッドを更新しています。
<p>
<label for="Title">Dinner Title:</label>
<%= Html.TextBox("Title", Model.Dinner.Title) %>
<%=Html.ValidationMessage("Title", "*") %>
</p>
<p>
<label for="Country">Country:</label>
<%= Html.DropDownList("Country", Model.Countries) %>
<%=Html.ValidationMessage("Country", "*") %>
</p>
また、エラーをレンダリングするときに DinnerFormViewModel クラスを使用するように Edit post メソッドを更新します。
//
// POST: /Dinners/Edit/5
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection collection) {
Dinner dinner = dinnerRepository.GetDinner(id);
try {
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
catch {
ModelState.AddModelErrors(dinner.GetRuleViolations());
return View(new DinnerFormViewModel(dinner));
}
}
また、Create() アクション メソッドを更新して、まったく同じ DinnerFormViewModel クラスを再利用して、それらの中で "Countries" DropDownList を有効にすることもできます。 HTTP-GET の実装を次に示します。
//
// GET: /Dinners/Create
public ActionResult Create() {
Dinner dinner = new Dinner() {
EventDate = DateTime.Now.AddDays(7)
};
return View(new DinnerFormViewModel(dinner));
}
HTTP-POST Create メソッドの実装を次に示します。
//
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Dinner dinner) {
if (ModelState.IsValid) {
try {
dinner.HostedBy = "SomeUser";
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
catch {
ModelState.AddModelErrors(dinner.GetRuleViolations());
}
}
return View(new DinnerFormViewModel(dinner));
}
これで、編集画面と作成画面の両方で、国または地域を選択するためのドロップダウン リストがサポートされるようになりました。
カスタムシェイプの ViewModel クラス
上記のシナリオでは、DinnerFormViewModel クラスは、サポートする SelectList モデル プロパティと共に、Dinner モデル オブジェクトをプロパティとして直接公開します。 この方法は、ビュー テンプレート内に作成する HTML UI が、ドメイン モデル オブジェクトに比較的近いシナリオに適しています。
そうでないシナリオの場合、使用できる 1 つのオプションは、オブジェクト モデルがビューによって使用できるように最適化され、基になるドメイン モデル オブジェクトとは完全に異なる可能性があるカスタムシェイプの ViewModel クラスを作成することです。 たとえば、複数のモデル オブジェクトから収集された異なるプロパティ名や集計プロパティを公開できる可能性があります。
カスタムシェイプの ViewModel クラスを使用すると、コントローラーからビューにデータを渡してレンダリングしたり、コントローラーのアクション メソッドにポストバックされたフォーム データを処理したりできます。 この後のシナリオでは、アクション メソッドで ViewModel オブジェクトをフォームに投稿されたデータで更新し、ViewModel インスタンスを使用して実際のドメイン モデル オブジェクトをマップまたは取得する場合があります。
カスタムシェイプの ViewModel クラスは非常に柔軟性が高く、ビュー テンプレート内のレンダリング コードやアクション メソッド内のフォーム投稿コードが複雑になり始めた場合にいつでも調査する必要があります。 これは、多くの場合、生成する UI にドメイン モデルが明らかに対応していないこと、および中間カスタムシェイプの ViewModel クラスが役立つことを示しています。
次の手順
ここでは、部分ページとマスター ページを使用して、アプリケーション全体で UI を再利用して共有する方法を見てみましょう。