ムービー コントローラーに関するアクション メソッドとビューを調べる
作成者: Rick Anderson
Note
ASP.NET MVC 5 と Visual Studio 2013 を使用するこのチュートリアルの更新バージョンは、こちらで入手できます。 より安全で、より簡単に操作でき、より多くの機能を備えています。
このセクションでは、ムービー コントローラーに関する生成対象のアクション メソッドとビューを調べます。 その後、カスタムの検索ページを追加します。
アプリケーションを実行し、ブラウザーのアドレス バーの URL に /Movies を追加して Movies
コントローラーを参照します。 Edit リンクの上にマウス ポインターを置くと、リンク先の URL が表示されます。
Edit リンクは、Views\Movies\Index.cshtml ビューで Html.ActionLink
メソッドによって生成されたものです。
@Html.ActionLink("Edit", "Edit", new { id=item.ID })
Html
オブジェクトは、System.Web.Mvc.WebViewPage 基底クラスのプロパティを使用して公開されるヘルパーです。 このヘルパーの ActionLink
メソッドを使用すると、コントローラー上のアクション メソッドにリンクする HTML ハイパーリンクを簡単かつ動的に生成できるようになります。 ActionLink
メソッドの最初の引数は、レンダリングするリンク テキストです (この例では <a>Edit Me</a>
)。 2 番目の引数は、呼び出すアクション メソッドの名前です。 最後の引数は、ルート データを生成する匿名オブジェクトです (この例では ID 4)。
上記のイメージでは、http://localhost:xxxxx/Movies/Edit/4
というリンクが生成されていることが示されています。 既定値のルート (App_Start\RouteConfig.cs で確立される) は、URL パターン {controller}/{action}/{id}
を受け取ります。 したがって、ASP.NET は、http://localhost:xxxxx/Movies/Edit/4
を、Movies
コントローラーの Edit
アクション メソッドへの要求に変換し、パラメーター ID
は 4 になります。 App_Start\RouteConfig.cs ファイル内の以下のコードを調べます。
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index",
id = UrlParameter.Optional }
);
}
クエリ文字列を使用してアクション メソッドのパラメーターを渡すこともできます。 たとえば、URL http://localhost:xxxxx/Movies/Edit?ID=4
でも Movies
コントローラーの Edit
アクション メソッドに ID
パラメーターとして 4 が渡されます。
Movies
コントローラーをオープンします。 以下に 2 つの Edit
アクション メソッドを示します。
//
// GET: /Movies/Edit/5
public ActionResult Edit(int id = 0)
{
Movie movie = db.Movies.Find(id);
if (movie == null)
{
return HttpNotFound();
}
return View(movie);
}
//
// POST: /Movies/Edit/5
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
2 番目の Edit
アクション メソッドの前に HttpPost
属性が付いていることに注意してください。 この属性は、POST 要求の場合のみ Edit
メソッドのオーバーロードを呼び出すことができることを指定します。 1 番目の Edit メソッドに HttpGet
属性を適用してもかまいませんが、既定値なので必要ありません。 (HttpGet
属性が暗黙的に割り当てられるアクション メソッドを HttpGet
メソッドと呼びます。)
HttpGet
Edit
メソッドは、ムービーの ID パラメーターを受け取り、Entity Framework の Find
メソッドを使ってムービーを検索して、選択されたムービーを Edit ビューに返します。 Edit
メソッドがパラメーターなしで呼び出される場合、ID パラメーターには 既定値の 0 が指定されます。 ムービーが見つからない場合は、HttpNotFound が返されます。 スキャフォールディング システムが編集ビューを作成したときは、そのシステムが Movie
クラスを調べて、クラスの各プロパティの <label>
および <input>
要素をレンダリングするコードを作成しました。 以下の例では、生成された Edit ビューを示します。
@model MvcMovie.Models.Movie
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
@Html.HiddenFor(model => model.ID)
<div class="editor-label">
@Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.ReleaseDate)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.ReleaseDate)
@Html.ValidationMessageFor(model => model.ReleaseDate)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Genre)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Genre)
@Html.ValidationMessageFor(model => model.Genre)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Price)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Price)
@Html.ValidationMessageFor(model => model.Price)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
ビュー テンプレートで、ファイルの先頭に @model MvcMovie.Models.Movie
ステートメントがあることに注目してください。このステートメントは、このビューではビュー テンプレートのモデルの型として Movie
が想定されていることを指定します。
スキャフォールディングされたコードは、複数のヘルパー メソッドを使って HTML マークアップを効率化します。 Html.LabelFor
ヘルパーは、フィールドの名前を表示します ("Title"、"ReleaseDate"、"Genre"、"Price")。 Html.EditorFor
ヘルパーは、HTML の <input>
要素をレンダリングします。 Html.ValidationMessageFor
ヘルパーは、そのプロパティに関連付けられている検証メッセージを表示します。
アプリケーションを実行し、/Movies URL に移動します。 [編集] リンクをクリックします。 ブラウザーで、ページのソースを表示します。 form 要素に関する HTML を以下に示します。
<form action="/Movies/Edit/4" method="post"> <fieldset>
<legend>Movie</legend>
<input data-val="true" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="4" />
<div class="editor-label">
<label for="Title">Title</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Title" name="Title" type="text" value="Rio Bravo" />
<span class="field-validation-valid" data-valmsg-for="Title" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="ReleaseDate">ReleaseDate</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-date="The field ReleaseDate must be a date." data-val-required="The ReleaseDate field is required." id="ReleaseDate" name="ReleaseDate" type="text" value="4/15/1959 12:00:00 AM" />
<span class="field-validation-valid" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Genre">Genre</label>
</div>
<div class="editor-field">
<input class="text-box single-line" id="Genre" name="Genre" type="text" value="Western" />
<span class="field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
</div>
<div class="editor-label">
<label for="Price">Price</label>
</div>
<div class="editor-field">
<input class="text-box single-line" data-val="true" data-val-number="The field Price must be a number." data-val-required="The Price field is required." id="Price" name="Price" type="text" value="2.99" />
<span class="field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
</form>
<input>
要素は、/Movies/Edit URL に送信するように action
属性が設定された HTML の <form>
要素に含まれます。 Edit ボタンがクリックされると、フォームのデータがサーバーに送信されます。
POST 要求の処理
次のリストでは、Edit
アクション メソッドの HttpPost
バージョンを示します。
[HttpPost]
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
ASP.NET MVC モデル バインダーは、送信されたフォーム値を取得し、movie
パラメーターとして渡される Movie
オブジェクトを作成します。 ModelState.IsValid
メソッドは、フォームで送信されたデータを使って Movie
オブジェクトを変更 (編集または更新) できることを検証します。 データが有効な場合、ムービー データは db(MovieDBContext
インスタンスの Movies
コレクションに保存されます。 新しいムービー データは、MovieDBContext
の SaveChanges
メソッドを呼び出すことによってデータベースに保存されます。 データを保存した後、コードはユーザーを MoviesController
クラスの Index
アクション メソッドにリダイレクトします。そこでは、行われたばかりの変更を含むムービー コレクションが表示されます。
送信された値が有効でない場合は、フォームに再表示されます。 Edit.cshtml ビュー テンプレートの Html.ValidationMessageFor
ヘルパーは、該当するエラー メッセージの表示を処理します。
Note
小数点にコンマ (「,」) を使用する英語以外のロケールで jQuery 検証をサポートするには、(https://github.com/jquery/globalize からの) globalize.js と特定の cultures/globalize.cultures.js ファイル、および Globalize.parseFloat
を使用する JavaScript を含める必要があります。 以下のコードは、「fr-FR」カルチャに対応するように Views\Movies\Edit.cshtml ファイルに加えた変更を示しています。
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script src="~/Scripts/globalize.js"></script>
<script src="~/Scripts/globalize.culture.fr-FR.js"></script>
<script>
$.validator.methods.number = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseFloat(value));
}
$(document).ready(function () {
Globalize.culture('fr-FR');
});
</script>
<script>
jQuery.extend(jQuery.validator.methods, {
range: function (value, element, param) {
//Use the Globalization plugin to parse the value
var val = $.global.parseFloat(value);
return this.optional(element) || (
val >= param[0] && val <= param[1]);
}
});
</script>
}
10 進フィールドに小数点ではなくコンマが必要な場合があります。 一時的な修正として、globalization 要素をプロジェクトのルートの web.config ファイルに追加できます。 以下のコードは、カルチャが米国英語に設定された globalization 要素を示しています。
<system.web>
<globalization culture ="en-US" />
<!--elements removed for clarity-->
</system.web>
すべての HttpGet
メソッドは同様のパターンに従います。 ムービー オブジェクト (または、Index
の場合はオブジェクトのリスト) を取得し、モデルをビューに渡します。 Create
メソッドは、空のムービー オブジェクトを Create ビューに渡します。 データの作成、編集、削除、またはそれ以外の変更を行うすべてのメソッドは、メソッドの HttpPost
のオーバーロードでそれを行います。 ブログ記事エントリ「ASP.NET MVC Tip #46 – Don't use Delete Links because they create Security Holes」で説明されているように、HTTP GET メソッドでデータを変更することはセキュリティ上のリスクになります。 GET メソッドでデータを変更することは、HTTP のベスト プラクティスや、GET 要求ではアプリケーションの状態を変更してはならないというアーキテクチャの REST パターンにも違反しています。 つまり、GET 操作の実行は、副作用がなく、永続化されたデータを変更しない、安全な操作である必要があります。
検索メソッドと検索ビューの追加
このセクションでは、SearchIndex
アクション メソッドを追加して、ジャンルまたは名前でムービーを検索できるようにします。 そのためには、/Movies/SearchIndex URL を使用します。 この要求により、ムービーを検索するためにユーザーが入力できる入力要素が含まれる HTML フォームが表示されます。 ユーザーがこのフォームを送信すると、ユーザーが送信した検索値がこのアクション メソッドにより取得され、その値を使用してデータベースが検索されます。
SearchIndex フォームの表示
まず、SearchIndex
アクション メソッドを既存の MoviesController
クラスに追加します。 このメソッドは、HTML フォームが含まれるビューを返します。 のコードを次に示します。
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
SearchIndex
メソッドの最初の行により、ムービーを選択する以下の LINQ クエリが作成されます。
var movies = from m in db.Movies
select m;
この時点では、このクエリが定義されていますが、まだデータ ストアに対して実行されていません。
searchString
パラメーターに文字列が含まれる場合、以下のコードを使用して、検索文字列の値でフィルターするようにムービー クエリが変更されます。
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
上の s => s.Title
コードはラムダ式です。 ラムダは、メソッド ベースの LINQ クエリで、上のコードで使用されている Where メソッドなど、標準クエリ演算子メソッドの引数として使用されます。 LINQ クエリは、Where
や OrderBy
などのメソッドの呼び出しで定義または変更されたときには実行されません。 その代わりに、クエリの実行が遅延します。つまり、その具体値が実際に繰り返されるか、ToList
メソッドが呼び出されるまで、式の評価が延期されます。 この SearchIndex
のサンプルでは、SearchIndex ビューでこのクエリが実行されます。 クエリの遅延実行の詳細については、「クエリの実行」を参照してください。
これで、フォームをユーザーに表示する SearchIndex
ビューを実装できるようになりました。 SearchIndex
メソッド内を右クリックし、[ビューの追加] をクリックします。 [ビューの追加] ダイアログ ボックスで、Movie
オブジェクトをモデル クラスとしてビュー テンプレートに渡すように指定します。 [スキャフォールディング テンプレート] のリストで、[リスト] を選択してから [追加] をクリックします。
[追加] ボタンをクリックすると、Views\Movies\SearchIndex.cshtml ビュー テンプレートが作成されます。 [スキャフォールディング テンプレート] リストで [リスト] を選択したので、Visual Studio によりビューに既定値のマークアップが自動的に生成 (スキャフォールディング) されました。 このスキャフォールディングにより HTML フォームが作成されました。 Movie
クラスが調べられ、このクラスのプロパティごとに <label>
要素をレンダリングするコードが作成されました。 以下のリストは、生成された Create ビューを示しています。
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewBag.Title = "SearchIndex";
}
<h2>SearchIndex</h2>
<p>
@Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
Title
</th>
<th>
ReleaseDate
</th>
<th>
Genre
</th>
<th>
Price
</th>
<th></th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.ReleaseDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.Genre)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
@Html.ActionLink("Details", "Details", new { id=item.ID }) |
@Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
アプリケーションを実行し、/Movies/SearchIndex に移動します。 ?searchString=ghost
などのクエリ文字列を URL に追加します。 フィルターされたムービーが表示されます。
id
という名前のパラメーターを使用するために SearchIndex
メソッドのシグネチャを変更すると、id
パラメーターは、Global.asax ファイルで設定されている既定ルートの {id}
プレースホルダーと一致するようになります。
{controller}/{action}/{id}
元の SearchIndex
メソッドは以下のとおりです。
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
変更後の SearchIndex
メソッドは以下のようになります。
public ActionResult SearchIndex(string id)
{
string searchString = id;
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
これで、クエリ文字列の値ではなく、ルート データ (URL セグメント) として検索タイトルを渡すことができます。
ただし、ユーザーがムービーを検索するたびに URL の変更を求めることはできません。 そのため、ここでは UI を追加して、ムービーをフィルターできるようにします。 ルート バインドされた ID パラメーターを渡す方法をテストするために SearchIndex
メソッドのシグネチャを変更した場合は、SearchIndex
メソッドが searchString
という名前の文字列パラメーターを受け取るようにシグネチャを元に戻します。
public ActionResult SearchIndex(string searchString)
{
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
return View(movies);
}
Views\Movies\SearchIndex.cshtml ファイルをオープンし、@Html.ActionLink("Create New", "Create")
の直後に以下を追加します。
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString")<br />
<input type="submit" value="Filter" /></p>
}
以下の例は、フィルター処理マークアップが追加された Views\Movies\SearchIndex.cshtml ファイルの一部を示しています。
@model IEnumerable<MvcMovie.Models.Movie>
@{
ViewBag.Title = "SearchIndex";
}
<h2>SearchIndex</h2>
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm()){
<p> Title: @Html.TextBox("SearchString") <br />
<input type="submit" value="Filter" /></p>
}
</p>
Html.BeginForm
ヘルパーは、開始 <form>
タグを作成します。 ユーザーが [フィルター] ボタンをクリックしてフォームを送信すると、Html.BeginForm
ヘルパーによりフォームがそれ自体に送信されます。
アプリケーションを実行し、ムービーを検索してみてください。
SearchIndex
メソッドの HttpPost
オーバーロードはありません。 メソッドではデータをフィルターするだけで、アプリケーションの状態を変更しないため、オーバーロードは必要ありません。
以下の HttpPost SearchIndex
メソッドを追加できます。 この場合、アクション呼び出し元が HttpPost SearchIndex
メソッドと一致し、HttpPost SearchIndex
メソッドが以下のイメージのように実行されます。
[HttpPost]
public string SearchIndex(FormCollection fc, string searchString)
{
return "<h3> From [HttpPost]SearchIndex: " + searchString + "</h3>";
}
ただし、この HttpPost
バージョンの SearchIndex
メソッドを追加しても、実装方法は制限されます。 たとえば、特定の検索をブックマークするか、友だちにリンクを送信し、友だちがそれをクリックしてムービーのフィルターされた同じリストを表示できるようにするとします。 HTTP POST 要求の URL は、GET 要求の URL (localhost:xxxxx/Movies/SearchIndex) と同じであり、URL 自体には検索情報がないことに注意してください。 この時点で、検索文字列情報はフォーム フィールド値としてサーバーに送信されます。 つまり、その検索情報をキャプチャしてブックマーク登録したり、URL で友人に送信したりすることはできません。
解決するには、BeginForm
のオーバーロードを使用して、POST 要求で検索情報を URL に追加して SearchIndex
メソッドの HttpGet バージョンにルーティングする必要があることを指定します。 既存のパラメーターなしの BeginForm
メソッドを以下に置き換えます。
@using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get))
ここで検索を送信すると、URL に検索クエリ文字列が含められます。 HttpPost SearchIndex
メソッドがある場合でも、検索時には HttpGet SearchIndex
アクション メソッドにも移動します。
ジャンルによる検索の追加
SearchIndex
メソッドの HttpPost
バージョンを追加した場合は、ここで削除します。
次に、ユーザーがジャンル別にムービーを検索できるようにする機能を追加します。 SearchIndex
メソッドを次のコードで置き換えます。
public ActionResult SearchIndex(string movieGenre, string searchString)
{
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
ViewBag.movieGenre = new SelectList(GenreLst);
var movies = from m in db.Movies
select m;
if (!String.IsNullOrEmpty(searchString))
{
movies = movies.Where(s => s.Title.Contains(searchString));
}
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
}
SearchIndex
メソッドのこのバージョンは、movieGenre
という名前の追加のパラメーターを受け取ります。 コードの最初の数行では、データベースからのムービーのジャンルを含める List
オブジェクトが作成されます。
次のコードは、データベースからすべてのジャンルを取得する LINQ クエリです。
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
このコードは、汎用 List
コレクションの AddRange
メソッドを使用して、重複しないジャンルをすべてリストに追加します。 (Distinct
修飾子がないと、ジャンルが重複して追加されます。たとえば、このサンプルでは「コメディ」が 2 回追加されます)。 その後、このコードはジャンルのリストを ViewBag
オブジェクトに保存します。
以下のコードは、movieGenre
パラメーターを検査する方法を示しています。 このコードは、このパラメーターが空でない場合にはムービー クエリの制約を厳しくし、指定したジャンルのムービーだけが選択されるように制限されるようにします。
if (string.IsNullOrEmpty(movieGenre))
return View(movies);
else
{
return View(movies.Where(x => x.Genre == movieGenre));
}
SearchIndex ビューにマークアップを追加してジャンル別の検索をサポートする
Views\Movies\SearchIndex.cshtml ファイル内の TextBox
ヘルパーの直前に Html.DropDownList
ヘルパーを追加します。 完成したマークアップを以下に示します。
<p>
@Html.ActionLink("Create New", "Create")
@using (Html.BeginForm("SearchIndex","Movies",FormMethod.Get)){
<p>Genre: @Html.DropDownList("movieGenre", "All")
Title: @Html.TextBox("SearchString")
<input type="submit" value="Filter" /></p>
}
</p>
アプリケーションを実行し、/Movies/SearchIndex を参照します。 ジャンル、ムービー名、その両方の条件で検索してみてください。
このセクションでは、フレームワークにより生成される CRUD のアクション メソッドとビューについて調べました。 ユーザーがムービーをタイトル別やジャンル別に検索できるようにする検索アクション メソッドとビューを作成しました。 次のセクションでは、Movie
モデルにプロパティを追加する方法と、テスト データベースを自動的に作成する初期化子を追加する方法について説明します。
フィードバック
https://aka.ms/ContentUserFeedback」を参照してください。
以下は間もなく提供いたします。2024 年を通じて、コンテンツのフィードバック メカニズムとして GitHub の issue を段階的に廃止し、新しいフィードバック システムに置き換えます。 詳細については、「フィードバックの送信と表示