次の方法で共有


ムービー コントローラーに関するアクション メソッドとビューを調べる

作成者: Rick Anderson

Note

ASP.NET MVC 5 と Visual Studio 2013 を使用するこのチュートリアルの更新バージョンは、こちらで入手できます。 より安全で、より簡単に操作でき、より多くの機能を備えています。

このセクションでは、ムービー コントローラーに関する生成対象のアクション メソッドとビューを調べます。 その後、カスタムの検索ページを追加します。

アプリケーションを実行し、ブラウザーのアドレス バーの URL に /Movies を追加して Movies コントローラーを参照します。 Edit リンクの上にマウス ポインターを置くと、リンク先の URL が表示されます。

EditLink_sm

Edit リンクは、Views\Movies\Index.cshtml ビューで Html.ActionLink メソッドによって生成されたものです。

@Html.ActionLink("Edit", "Edit", new { id=item.ID })

Html.ActionLink

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 が渡されます。

EditQueryString

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 メソッドと呼びます。)

HttpGetEdit メソッドは、ムービーの 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 コレクションに保存されます。 新しいムービー データは、MovieDBContextSaveChanges メソッドを呼び出すことによってデータベースに保存されます。 データを保存した後、コードはユーザーを MoviesController クラスの Index アクション メソッドにリダイレクトします。そこでは、行われたばかりの変更を含むムービー コレクションが表示されます。

送信された値が有効でない場合は、フォームに再表示されます。 Edit.cshtml ビュー テンプレートの Html.ValidationMessageFor ヘルパーは、該当するエラー メッセージの表示を処理します。

abcNotValid

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 クエリは、WhereOrderBy などのメソッドの呼び出しで定義または変更されたときには実行されません。 その代わりに、クエリの実行が遅延します。つまり、その具体値が実際に繰り返されるか、ToList メソッドが呼び出されるまで、式の評価が延期されます。 この SearchIndex のサンプルでは、SearchIndex ビューでこのクエリが実行されます。 クエリの遅延実行の詳細については、「クエリの実行」を参照してください。

これで、フォームをユーザーに表示する SearchIndex ビューを実装できるようになりました。 SearchIndex メソッド内を右クリックし、[ビューの追加] をクリックします。 [ビューの追加] ダイアログ ボックスで、Movie オブジェクトをモデル クラスとしてビュー テンプレートに渡すように指定します。 [スキャフォールディング テンプレート] のリストで、[リスト] を選択してから [追加] をクリックします。

AddSearchView

[追加] ボタンをクリックすると、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 に追加します。 フィルターされたムービーが表示されます。

SearchQryStr

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 セグメント) として検索タイトルを渡すことができます。

SearchRouteData

ただし、ユーザーがムービーを検索するたびに 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 ヘルパーによりフォームがそれ自体に送信されます。

アプリケーションを実行し、ムービーを検索してみてください。

Screenshot of running the application and trying to search for a movie.

SearchIndex メソッドの HttpPost オーバーロードはありません。 メソッドではデータをフィルターするだけで、アプリケーションの状態を変更しないため、オーバーロードは必要ありません。

以下の HttpPost SearchIndex メソッドを追加できます。 この場合、アクション呼び出し元が HttpPost SearchIndex メソッドと一致し、HttpPost SearchIndex メソッドが以下のイメージのように実行されます。

[HttpPost] 
public string SearchIndex(FormCollection fc, string searchString) 
{ 
    return "<h3> From [HttpPost]SearchIndex: " + searchString + "</h3>"; 
}

SearchPostGhost

ただし、この 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))

BeginFormPost_SM

ここで検索を送信すると、URL に検索クエリ文字列が含められます。 HttpPost SearchIndex メソッドがある場合でも、検索時には HttpGet SearchIndex アクション メソッドにも移動します。

SearchIndexWithGetURL

ジャンルによる検索の追加

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 を参照します。 ジャンル、ムービー名、その両方の条件で検索してみてください。

Screenshot of running the application and trying to search by genre movie name and by both criteria.

このセクションでは、フレームワークにより生成される CRUD のアクション メソッドとビューについて調べました。 ユーザーがムービーをタイトル別やジャンル別に検索できるようにする検索アクション メソッドとビューを作成しました。 次のセクションでは、Movie モデルにプロパティを追加する方法と、テスト データベースを自動的に作成する初期化子を追加する方法について説明します。