パート 3: Views と ViewModels
作成者: Jon Galloway
MVC Music Store は、ASP.NET MVC と Visual Studio を使用した Web 開発の手順を段階的に紹介し、説明するチュートリアル アプリケーションです。
MVC Music Store は、音楽アルバムをオンラインで販売する軽量なサンプル ストアの実装です。基本的なサイト管理、ユーザー サインイン、ショッピング カート機能を実装しています。
このチュートリアル シリーズでは、ASP.NET MVC Music Store サンプル アプリケーションを作成するために必要なすべての手順について詳しく説明します。 パート 3 では、ビューとビューモデルについて説明します。
これまでは、コントローラー アクションから文字列を返しただけでした。 これはコントローラーのしくみを理解するには便利ですが、このように実際の Web アプリケーションを構築したいとは思わないでしょう。 HTML を生成してサイトにアクセスしたブラウザーに返すより優れた方法 - テンプレート ファイルを使って送信する HTML コンテンツをより簡単にカスタマイズできる方法が必要になります。 ビューはまさにそのような役割を果たします。
ビュー テンプレートの追加
ビュー テンプレートを使用するには、HomeController の Index メソッドを ActionResult を返すように変更し、次のように View() を返すようにします。
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
上記の変更は、文字列を返す代わりに、"View" を使って返す結果を生成したいということを示しています。
次に、適切なビュー テンプレートをプロジェクトに追加します。 そのためには、Index アクション メソッド内にテキスト カーソルを置き、右クリックして [ビューの追加] を選択します。 これにより [ビューの追加] ダイアログが表示されます。
[ビューの追加] ダイアログを使うと、ビュー テンプレート ファイルをすばやく簡単に生成できます。 [ビューの追加] ダイアログでは既定で、作成するビュー テンプレートの名前が、それを使用するアクション メソッドと一致するように事前に入力されます。 ここでは HomeController の Index() アクション メソッド内で [ビューの追加] コンテキスト メニューを使用したので、上記の [ビューの追加] ダイアログでは "Index" が既定でビュー名として事前入力されています。 このダイアログのオプションを変更する必要はないので、[追加] ボタンをクリックします。
[追加] ボタンをクリックすると、Visual Web Developer によって新しい Index.cshtml ビュー テンプレートが \Views\Home ディレクトリに作成されます。このフォルダーがまだ存在しない場合は作成されます。
"Index.cshtml" ファイルの名前とフォルダーの場所は重要であり、既定の ASP.NET MVC 名前付け規則に従っています。 ディレクトリ名 \Views\Home は、コントローラー (HomeController という名前) と一致します。 ビュー テンプレート名 Index は、ビューを表示するコントローラー アクション メソッドと一致します。
ASP.NET MVC では、この名前付け規則を使ってビューを返す場合、ビュー テンプレートの名前または場所を明示的に指定する必要がなくなります。 これにより、HomeController 内に次のようなコードを記述すれば、既定で \Views\Home\Index.cshtml ビュー テンプレートがレンダリングされます。
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
[ビューの追加] ダイアログで [追加] ボタンをクリックした後、Visual Web Developer によって "Index.cshtml" ビュー テンプレートが作成され、開かれました。 Index.cshtml の内容を次に示します。
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
このビューでは Razor 構文が使われています。これは、ASP.NET Web Forms と以前のバージョンの ASP.NET MVC で使われている Web Forms ビュー エンジンよりも簡潔です。 ASP.NET MVC 3 でも Web Forms ビュー エンジンを使用できますが、多くの開発者にとっては Razor ビュー エンジンの方が ASP.NET MVC 開発に適しているでしょう。
最初の 3 行では、ViewBag.Title を使ってページ タイトルを設定しています。 そのしくみについてはすぐに詳しく説明しますが、まずはテキスト見出しのテキストを更新してページを表示してみましょう。 次のように、<h2> タグを更新して、"This is the Home Page" と表示します。
@{
ViewBag.Title = "Index";
}
<h2>This is the Home Page</h2>
アプリケーションを実行すると、新しいテキストがホーム ページに表示されます。
共通するサイト要素用に Layout を使う
ほとんどの Web サイトには、多くのページ間で共有されるコンテンツがあります。たとえば、ナビゲーション、フッター、ロゴ画像、スタイルシート参照などです。Razor ビュー エンジンを使うと、_Layout.cshtml というページを使ってこれを簡単に管理できます。このファイルは /Views/Shared フォルダー内に自動的に作成されています。
このフォルダーをダブルクリックして、内容を表示します (次に示します)。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")"
rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")"
type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")"
type="text/javascript"></script>
</head>
<body>
@RenderBody()
</body>
</html>
個々のビューのコンテンツは @RenderBody() コマンドによって表示され、その外側に表示したい共通コンテンツは _Layout.cshtml マークアップに追加できます。 MVC Music Store では、Home ページと Store 領域へのリンクを含む共通ヘッダーをサイト内の全ページに表示します。そこで、テンプレートの @RenderBody() ステートメントのすぐ上にそのヘッダーを追加します。
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")"
rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")"
type="text/javascript"></script>
</head>
<body>
<div id="header">
<h1>
ASP.NET MVC MUSIC STORE</h1>
<ul id="navlist">
<li class="first"><a href="/"
id="current">Home</a></li>
<li><a
href="/Store/">Store</a></li>
</ul>
</div>
@RenderBody()
</body>
</html>
スタイルシートの更新
空のプロジェクト テンプレートには、確認メッセージを表示するために使われるスタイルのみを含む、非常に合理化された CSS ファイルが含まれています。 デザイナーは、サイトの外観を定義する追加の CSS と画像をいくつか提供しているので、ここでそれらを追加します。
更新された CSS ファイルと画像は、MVC-Music-Store で入手できる MvcMusicStore-Assets.zip の Content ディレクトリに含まれています。 次に示すように、Windows エクスプローラーでその両方を選択し、Visual Web Developer のソリューションの Content フォルダーにドロップします。
既存の Site.css ファイルを上書きするかどうかを確認するメッセージが表示されます。 [はい] をクリックします。
これで、アプリケーションの Content フォルダーは次のようになります。
次に、アプリケーションを実行して、変更内容が Home ページでどのように表示されるかを確認しましょう。
- 変更された内容を確認しましょう。コードで "return View()" を呼び出しているにもかかわらず、HomeController の Index アクション メソッドが \Views\Home\Index.cshtmlView テンプレートを見つけて表示しました。View テンプレートが標準の名前付け規則に従っていたためです。
- Home ページには、\Views\Home\Index.cshtml ビュー テンプレート内で定義されているシンプルなウェルカム メッセージが表示されます。
- Home ページでは _Layout.cshtml テンプレートが使用されるため、ウェルカム メッセージは標準のサイト HTML レイアウトに含まれています。
モデルを使ってビューに情報を渡す
ハードコーディングした HTML を表示するだけのビュー テンプレートでは、あまり興味深い Web サイトは作成できません。 動的な Web サイトを作成するには、代わりにコントローラー アクションからビュー テンプレートに情報を渡す必要があります。
Model-View-Controller パターンでは、モデルという用語はアプリケーション内のデータを表すオブジェクトを指します。 多くの場合、モデル オブジェクトはデータベース内のテーブルに対応していますが、必須というわけではありません。
ActionResult を返すコントローラー アクション メソッドは、ビューにモデル オブジェクトを渡すことができます。 これにより、コントローラーで応答を生成するために必要なすべての情報を明確にパッケージ化し、その情報をビュー テンプレートに渡して、適切な HTML 応答の生成に使用することができます。 実際の動作を見て理解するのが一番簡単なので、作業を開始しましょう。
まず、ストア内のジャンルとアルバムを表す Model クラスをいくつか作成します。 最初に Genre クラスを作成しましょう。 プロジェクト内の "Models" フォルダーを右クリックし、[クラスの追加] オプションを選択して、ファイルに "Genre.cs" という名前を付けます。
次に、作成されたクラスに public string Name プロパティを追加します。
public class Genre
{
public string Name { get; set; }
}
注: 気になる場合のために、{ get; set; } という表記は C# の自動実装プロパティ機能を使用したものです。 これにより、バッキング フィールドを宣言しなくてもプロパティを利用することができます。
次に、同じ手順に従って、Title プロパティと Genre プロパティを持つ Album クラス (Album.cs という名前) を作成します。
public class Album
{
public string Title { get; set; }
public Genre Genre { get; set; }
}
これで、モデルからの動的な情報を表示するビューを使うように StoreController を変更できます。 (ここだけのデモンストレーションの目的で) 要求 ID に基づいてアルバムに名前を付ける場合は、その情報を次のビューのように表示できます。
まず、Store の Details アクションを変更して、1 つのアルバムの情報が表示されるようにします。 StoreControllers クラスの先頭に "using" ステートメントを追加して、MvcMusicStore.Models 名前空間を含めます。これで、アルバムのクラスを使うたびに MvcMusicStore.Models.Album と入力する必要がなくなります。 このクラスの "using" セクションは次のようになります。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MvcMusicStore.Models;
次に、Details コントローラー アクションを更新して、HomeController の Index メソッドと同様に、文字列ではなく ActionResult を返すようにします。
public ActionResult Details(int id)
これで、ビューに Album オブジェクトを返すようにロジックを変更できます。 このチュートリアルの後半ではデータベースからデータを取得しますが、現時点では "ダミー データ" を使って手順を進めます。
public ActionResult Details(int id)
{
var album = new Album { Title = "Album " + id };
return View(album);
}
注: C# に慣れていない場合は、var を使っているのでアルバムの変数が遅延バインディングされると思うかもしれません。 これは正しくありません。C# コンパイラは、変数に代入している内容に基づいて型推論を使ってこの album が Album 型であると判断し、ローカル album 変数を Album 型としてコンパイルするため、コンパイル時のチェックと Visual Studio コード エディターのサポートが得られます。
次に、Album を使って HTML 応答を生成するビュー テンプレートを作成しましょう。 それを行う前に、新しく作成した Album クラスを [ビューの追加] ダイアログで認識できるように、プロジェクトをビルドする必要があります。 プロジェクトをビルドするには、[デバッグ] ⇨ [MvcMusicStore のビルド] メニュー項目を選択します (オプション: Ctrl + Shift + B ショートカットを使ってプロジェクトをビルドできます)。
サポート クラスの設定が完了したので、ビュー テンプレートを作成する準備ができました。 Details メソッド内を右クリックし、コンテキスト メニューから [ビューの追加...] を選択します。
前の HomeController の場合と同様に、新しいビュー テンプレートを作成します。 StoreController から作成するので、それは既定で \Views\Store\Index.cshtml ファイルに生成されます。
以前とは異なり、[厳密に型指定されたビューを作成する] チェックボックスをオンにします。 次に、[ビューのデータ クラス] ドロップダウンの一覧から "Album" クラスを選択します。 これにより、Album オブジェクトが渡されて使用されることを想定したビュー テンプレートが [ビューの追加] ダイアログによって作成されます。
[追加] ボタンをクリックすると、次のコードを含む \Views\Store\Details.cshtml ビュー テンプレートが作成されます。
@model MvcMusicStore.Models.Album
@{
ViewBag.Title = "Details";
}
<h2>Details</h2>
このビューが Album クラスに厳密に型指定されていることを示す最初の行に注目してください。 Razor ビュー エンジンは、Album オブジェクトが渡されたことを認識するため、モデルのプロパティに簡単にアクセスでき、Visual Web Developer エディターの IntelliSense の恩恵を受けることもできます。
<h2> タグを更新し、その行を次のように変更して、Album の Title プロパティを表示します。
<h2>Album: @Model.Title</h2>
@Model キーワードの後にピリオドを入力すると IntelliSense がトリガーされ、Album クラスでサポートされているプロパティとメソッドが表示されるのがわかります。
プロジェクトを再実行し、/Store/Details/5 という URL にアクセスしてみましょう。 以下のような Album の詳細が表示されます。
次に、Store の Browse アクション メソッドについても同様の更新を行います。 ActionResult を返すようにメソッドを更新し、新しい Genre オブジェクトを作成してビューに返すようにメソッドのロジックを変更します。
public ActionResult Browse(string genre)
{
var genreModel = new Genre { Name = genre };
return View(genreModel);
}
Browse メソッドを右クリックし、コンテキスト メニューから [ビューの追加...] を選択します。その後、Genre クラスに厳密に型指定されたビューを追加します。
ビュー コード (/Views/Store/Browse.cshtml) の <h2> 要素を更新して、Genre の情報を表示します。
@model MvcMusicStore.Models.Genre
@{
ViewBag.Title = "Browse";
}
<h2>Browsing Genre: @Model.Name</h2>
次に、プロジェクトを再実行し、/Store/Browse?Genre=Disco という URL を参照してみましょう。 以下のような Browse ページが表示されます。
最後に、Store の Index アクション メソッドとビューにもう少し複雑な更新を加えて、ストア内のすべてのジャンルの一覧を表示しましょう。 これを行うために、1 つの Genre ではなく、Genre の List をモデル オブジェクトとして使用します。
public ActionResult Index()
{
var genres = new List<Genre>
{
new Genre { Name = "Disco"},
new Genre { Name = "Jazz"},
new Genre { Name = "Rock"}
};
return View(genres);
}
Store の Index アクション メソッドを右クリックし、以前と同様に [ビューの追加] を選択して、モデル クラスとして [Genre] を選択し、[追加] ボタンを押します。
まず、@model 宣言を変更して、ビューが 1 つではなく複数の Genre オブジェクトを想定することを示します。 /Store/Index.cshtml の最初の行を、次のように変更します。
@model IEnumerable<MvcMusicStore.Models.Genre>
これにより、複数の Genre オブジェクトを保持できるモデル オブジェクトを操作することを Razor ビュー エンジンに通知します。 List<Genre> ではなく IEnumerable<Genre> を使用しているのは、こちらの方が汎用的であり、後でモデルの種類を IEnumerable インターフェイスをサポートする任意のオブジェクトの種類に変更できるためです。
次に、以下の完成したビュー コードに示すように、モデル内の各 Genre オブジェクトをループ処理します。
@model IEnumerable<MvcMusicStore.Models.Genre>
@{
ViewBag.Title = "Store";
}
<h3>Browse Genres</h3>
<p>
Select from @Model.Count()
genres:</p>
<ul>
@foreach (var genre in Model)
{
<li>@genre.Name</li>
}
</ul>
このコードを入力する際に IntelliSense の完全なサポートを利用でき、「@Model.」と入力すると Genre 型の IEnumerable でサポートされているすべてのメソッドとプロパティが表示されることがわかります。
"foreach" ループ内で、Visual Web Developer は各項目が Genre 型であることを認識しているため、Genre 型ごとに IntelliSense が表示されます。
次に、スキャフォールディング機能が Genre オブジェクトを調べ、それぞれに Name プロパティがあることがわかるので、ループ処理してそれらを書き出します。また、個々の項目に対する Edit、Details、Delete のリンクも生成されます。 これはストア マネージャーで後ほど利用しますが、ここでは代わりにシンプルなリストを使用します。
アプリケーションを実行して /Store を参照すると、ジャンルの数と一覧の両方が表示されます。
ページ間のリンクの追加
現在、ジャンルを一覧表示する /Store の URL は、Genre の名前をプレーンテキストとして表示するだけです。 これを変更して、プレーンテキストの代わりに、Genre の名前を適切な /Store/Browse の URL にリンクさせましょう。たとえば、"Disco" などの音楽ジャンルをクリックすると、/Store/Browse?genre=Disco という URL に移動します。 このようなリンクを出力するために、次のようなコードを使って \Views\Store\Index.cshtml ビュー テンプレートを更新できます (このコードはこれから改善するため、入力しないでください)。
<ul>
@foreach (var genre in Model)
{
<li><a href="/Store/Browse?genre=@genre.Name">@genre.Name</a></li>
}
</ul>
これは機能しますが、ハードコーディングされた文字列に依存するため、後で問題が発生するおそれがあります。 たとえば、コントローラーの名前を変更したい場合は、コードを検索して更新する必要があるリンクを探す必要があります。
使用できる別の方法は、HTML ヘルパー メソッドを利用することです。 ASP.NET MVC には、ビュー テンプレート コードから使用できる HTML ヘルパー メソッドが含まれており、このような各種の一般的なタスクを実行できます。 Html.ActionLink() ヘルパー メソッドは特に便利なメソッドです。HTML の <a> リンクを簡単に作成でき、URL パスが適切に URL エンコードされていることを確認するといった、面倒な手順を処理できます。
Html.ActionLink() にはいくつかの異なるオーバーロードがあり、リンクに必要な情報を柔軟に指定できます。 最もシンプルなケースでは、リンク テキストと、クライアント側でハイパーリンクがクリックされたときに移動するアクション メソッドのみを指定します。 たとえば、次の呼び出しを使用して、"Go to the Store Index" というリンク テキストで Store Details ページの "/Store/" Index() メソッドにリンクできます。
@Html.ActionLink("Go
to the Store Index", "Index")
"注: このケースでは、現在のビューをレンダリングしている同じコントローラー内の別のアクションにリンクしているだけなので、コントローラー名を指定する必要はありませんでした。"
ただし、Browse ページへのリンクではパラメーターを渡す必要があるため、次の 3 つのパラメーターを受け取る Html.ActionLink メソッドの別のオーバーロードを使用します。
-
- ジャンル名を表示するリンク テキスト
-
- コントローラー アクション名 (Browse)
-
- 名前 (ジャンル) と値 (ジャンル名) の両方を指定するルート パラメーターの値
これらすべてをまとめて、そのような Store Index ビューへのリンクを記述する方法を次に示します。
<ul>
@foreach (var genre in Model)
{
<li>@Html.ActionLink(genre.Name,
"Browse", new { genre = genre.Name })</li>
}
</ul>
プロジェクトをもう一度実行して /Store/ の URL にアクセスすると、ジャンルの一覧が表示されます。 各ジャンルはハイパーリンクになっており、クリックすると /Store/Browse?genre=<ジャンル> という URL に移動します。
ジャンル一覧の HTML は次のようになります。
<ul>
<li><a href="/Store/Browse?genre=Disco">Disco</a>
</li>
<li><a href="/Store/Browse?genre=Jazz">Jazz</a>
</li>
<li><a href="/Store/Browse?genre=Rock">Rock</a>
</li>
</ul>