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

作成者: Rick Anderson

Note

Visual Studio の最新バージョンを使用した、このチュートリアルの更新バージョンはこちらにあります。 新しいチュートリアル (このチュートリアルよりも多くの改善がされています) では、ASP.NET Core MVC を使用しています。

このチュートリアルでは、ASP.NET Core MVC のコントローラーとビューについて説明します。 Razor Pages は ASP.NET Core での新しい代替手段であり、Web UI の構築をより簡単かつ生産的にする、ページベースのプログラミング モデルです。 MVC のバージョンの前に、Razor ページのチュートリアルを試すことをお勧めします。 この Razor ページのチュートリアルの特徴は次のとおりです。

  • 使いやすい。
  • 多くの機能をカバーしている。
  • 新しいアプリ開発に最適なアプローチである。

このセクションでは、ムービー コントローラー用に生成された Edit アクション メソッドとビューを調べます。 しかし、その前に、リリース日をより良く見せるために、少し寄り道をしましょう。 Models\Movie.cs ファイルを開き、以下に示す強調表示された行を追加します。

using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;

namespace MvcMovie.Models
{
    public class Movie
    {
        public int ID { get; set; }
        public string Title { get; set; }

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }
        public decimal Price { get; set; }
    }

    public class MovieDBContext : DbContext
    {
        public DbSet<Movie> Movies { get; set; }
    }
}

日付カルチャを次のように指定することもできます。

[Display(Name = "Release Date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime ReleaseDate { get; set; }

DataAnnotations については、次のチュートリアルで説明します。 Display 属性は、フィールドの名前として表示する内容 (ここでは、"ReleaseDate" ではなく、"Release Date") を指定します。 DataType 属性はデータ型を指定します。この場合は日付なので、フィールドに格納されている時間情報は表示されません。 DisplayFormat 属性は、日付形式が正しくレンダリングされない Chrome ブラウザーのバグに対処するために必要です。

アプリケーションを実行し、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 メソッドの第 1 引数は、リンクとして表示するテキストです (例: <a>Edit Me</a>)。 第 2 引数は、呼び出すアクション メソッドの名前です (この場合は、Edit アクション)。 最後の引数は、ルート データを生成する匿名オブジェクトです (この場合は ID 4)。

上記のイメージでは、http://localhost:1234/Movies/Edit/4 というリンクが生成されていることが示されています。 既定値のルート (App_Start\RouteConfig.cs で確立される) は、URL パターン {controller}/{action}/{id} を受け取ります。 したがって、ASP.NET は、http://localhost:1234/Movies/Edit/4 を、Movies コントローラーの Edit アクション メソッドへの要求に変換し、パラメーター ID は 4 になります。 App_Start\RouteConfig.cs ファイル内の以下のコードを調べます。 MapRoute メソッドは、HTTP 要求を正しいコントローラーとアクション メソッドにルーティングし、オプションの ID パラメーターを指定するために使用されます。 MapRoute メソッドは、コントローラー、アクション メソッド、ルート データを指定して URL を生成するために、ActionLink などの HtmlHelpers でも使用されます。

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:1234/Movies/Edit?ID=3Movies コントローラーの Edit アクション メソッドにパラメーター ID 3 を渡します。

EditQueryString

Movies コントローラーを開きます。 2 つの Edit アクション メソッドを以下に示します。

// GET: /Movies/Edit/5
public ActionResult Edit(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Movie movie = db.Movies.Find(id);
    if (movie == null)
    {
        return HttpNotFound();
    }
    return View(movie);
}

// POST: /Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Entry(movie).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(movie);
}

2 番目の Edit アクション メソッドの前に HttpPost 属性が付いていることに注意してください。 この属性は、Edit メソッドのオーバーロードは POST 要求の場合にのみ呼び出し可能であることを指定するものです。 第 1 の Edit メソッドには HttpGet 属性を適用できますが、そちらは既定で適用済みの扱いになるため必須ではありません (HttpGet 属性が暗黙的に割り当てられているアクション メソッドを HttpGet メソッドと呼びます)。Bind 属性は、ハッカーがデータをモデルに過剰に投稿するのを防ぐもう 1 つの重要なセキュリティ メカニズムです。 変更する bind 属性にだけプロパティを含める必要があります。 過剰ポスティングと bind 属性については、過剰ポスティングのセキュリティに関するメモで読むことができます。 このチュートリアルで使用する単純なモデルでは、モデル内のすべてのデータをバインドします。 ValidateAntiForgeryToken 属性は、要求の偽造を防ぐために使用され、編集ビュー ファイル (Views\Movies\Edit.cshtml) の @Html.AntiForgeryToken() とペアになっています。その一部を次に示します。

@model MvcMovie.Models.Movie

@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>

@Html.AntiForgeryToken() は、Movies コントローラーの Edit メソッドで一致する必要がある、非表示のフォーム偽造防止トークンを生成します。 クロスサイト リクエスト フォージェリ (XSRF または CSRF とも呼ばれます) の詳細については、MVC の XSRF/CSRF 防止に関するチュートリアルを参照してください。

HttpGet Edit メソッドは movie ID パラメーターを受け取り、Entity Framework Find メソッドを使用してムービーを検索し、選択したムービーを編集ビューに返します。 ムービーが見つからない場合は、HttpNotFound が返されます。 スキャフォールディング システムが編集ビューを作成したときは、そのシステムが Movie クラスを調べて、クラスの各プロパティの <label> および <input> 要素をレンダリングするコードを作成しました。 次の例では、Visual Studio のスキャフォールディング システムによって生成された編集ビューを示します。

@model MvcMovie.Models.Movie

@{
    ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()    
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.ID)

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.ReleaseDate, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.ReleaseDate)
                @Html.ValidationMessageFor(model => model.ReleaseDate)
            </div>
        </div>
        @*Genre and Price removed for brevity.*@        
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}
<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">
   <input name="__RequestVerificationToken" type="hidden" value="UxY6bkQyJCXO3Kn5AXg-6TXxOj6yVBi9tghHaQ5Lq_qwKvcojNXEEfcbn-FGh_0vuw4tS_BRk7QQQHlJp8AP4_X4orVNoQnp2cd8kXhykS01" />  <fieldset class="form-horizontal">
      <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="control-group">
         <label class="control-label" for="Title">Title</label>
         <div class="controls">
            <input class="text-box single-line" id="Title" name="Title" type="text" value="GhostBusters" />
            <span class="field-validation-valid help-inline" data-valmsg-for="Title" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="ReleaseDate">Release Date</label>
         <div class="controls">
            <input class="text-box single-line" data-val="true" data-val-date="The field Release Date must be a date." data-val-required="The Release Date field is required." id="ReleaseDate" name="ReleaseDate" type="date" value="1/1/1984" />
            <span class="field-validation-valid help-inline" data-valmsg-for="ReleaseDate" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="Genre">Genre</label>
         <div class="controls">
            <input class="text-box single-line" id="Genre" name="Genre" type="text" value="Comedy" />
            <span class="field-validation-valid help-inline" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="control-group">
         <label class="control-label" for="Price">Price</label>
         <div class="controls">
            <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="7.99" />
            <span class="field-validation-valid help-inline" data-valmsg-for="Price" data-valmsg-replace="true"></span>
         </div>
      </div>

      <div class="form-actions no-color">
         <input type="submit" value="Save" class="btn" />
      </div>
   </fieldset>
</form>

<input> 要素は、/Movies/Edit URL に送信するように action 属性が設定された HTML の <form> 要素に含まれます。 [保存] ボタンがクリックされると、フォームのデータがサーバーに送信されます。 2 行目には、@Html.AntiForgeryToken() 呼び出しによって生成された非表示の XSRF トークンが表示されます。

POST 要求の処理

次のリストでは、Edit アクション メソッドの HttpPost バージョンを示します。

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (ModelState.IsValid)
    {
        db.Entry(movie).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(movie);
}

ValidateAntiForgeryToken 属性は、ビュー内の @Html.AntiForgeryToken() 呼び出しによって生成された XSRF トークンを検証します。

ASP.NET MVC モデル バインダーは、送信されたフォーム値を取得し、movie パラメーターとして渡される Movie オブジェクトを作成します。 ModelState.IsValid は、フォームで送信されたデータを使って Movie オブジェクトを変更 (編集または更新) できることを検証します。 データが有効な場合、ムービー データは db (MovieDBContextインスタンス) の Movies コレクションに保存されます。 新しいムービー データは、MovieDBContextSaveChanges メソッドを呼び出すことによってデータベースに保存されます。 データを保存した後、コードはユーザーを MoviesController クラスの Index アクション メソッドにリダイレクトします。そこでは、行われたばかりの変更を含むムービー コレクションが表示されます。

クライアント側の検証でフィールドの値が無効であると判断されると、すぐにエラー メッセージが表示されます。 JavaScript が無効になっている場合、クライアント側の検証は無効になります。 ただし、ポストされた値が有効でないことがサーバーによって検出され、そのフォームの値がエラー メッセージとともに再表示されます。

検証については、チュートリアルの後半で詳しく説明します。

Edit.cshtml ビュー テンプレートの Html.ValidationMessageFor ヘルパーは、該当するエラー メッセージの表示を処理します。

abcNotValid

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 操作の実行は、副作用がなく、永続化されたデータを変更しない、安全な操作である必要があります。

英語以外のロケールの jQuery 検証

英語 (米国) のコンピューターを使用している場合は、このセクションをスキップして、次のチュートリアルに進むことができます。 このチュートリアル の Globalize バージョンは、こちらからダウンロードできます。 国際化に関する優れた 2 部構成のチュートリアルについては、Nadeem の ASP.NET MVC 5 の国際化を参照してください。

Note

小数点にコンマ (「,」) を使用する英語以外のロケールや、英語 (米国) 以外の日付形式の jQuery 検証をサポートするには、globalize.js と特定の cultures/globalize.cultures.js ファイル (https://github.com/jquery/globalize から)、および Globalize.parseFloat を使用する JavaScript を含める必要があります。 jQuery の英語以外の検証は NuGet から取得できます (英語のロケールを使用している場合は、Globalize をインストールしないでください)。

  1. [ツール] メニューで、[NuGet パッケージ マネージャー] をクリックし、次に [ソリューションの NuGet パッケージの管理] をクリックします。

    英語以外のロケールの jQuery 検証を開始するための [ツール] メニューのスクリーンショット。

  2. 左側のペインで、[参照] を選択します (下の画像を参照)。

  3. 入力ボックスに「Globalize」と入力します。

    Globalize と入力する入力ボックスのスクリーンショット。

    jQuery.Validation.Globalize を選択し、MvcMovie を選び [インストール] をクリックします。 Scripts\jquery.globalize\globalize.js ファイルがプロジェクトに追加されます。 *Scripts\jquery.globalize\culture* フォルダーには、多くのカルチャ JavaScript ファイルが含まれます。 このパッケージのインストールには 5 分かかる場合があることに注意してください。

    次のコードは、Views\Movies\Edit.cshtml ファイルへの変更を示しています。

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")

<script src="~/Scripts/globalize/globalize.js"></script>
<script src="~/Scripts/globalize/cultures/globalize.culture.@(System.Threading.Thread.CurrentThread.CurrentCulture.Name).js"></script>
<script>
    $.validator.methods.number = function (value, element) {
        return this.optional(element) ||
            !isNaN(Globalize.parseFloat(value));
    }
    $(document).ready(function () {
        Globalize.culture('@(System.Threading.Thread.CurrentThread.CurrentCulture.Name)');
    });
</script>
<script>
    jQuery.extend(jQuery.validator.methods, {
        range: function (value, element, param) {
            //Use the Globalization plugin to parse the value
            var val = Globalize.parseFloat(value);
            return this.optional(element) || (
                val >= param[0] && val <= param[1]);
        }
    });
    $.validator.methods.date = function (value, element) {
        return this.optional(element) ||
            Globalize.parseDate(value) ||
            Globalize.parseDate(value, "yyyy-MM-dd");
    }
</script>
}

すべての編集ビューでこのコードが繰り返されないようにするために、このコードをレイアウト ファイルに移動できます。 スクリプトのダウンロードを最適化するには、バンドルと縮小のチュートリアルを参照してください。

詳細については、「ASP.NET MVC 3 の国際化」と「MVC 3 の国際化の ASP.NET - パート 2 (NerdDinner)」を参照してください。

ロケールで検証が機能しない場合は、一時的な修正として、コンピューターで英語 (米国) を使用するように強制するか、ブラウザーで JavaScript を無効にすることができます。 コンピューターで英語 (米国) を使用するように強制するには、グローバリゼーション要素をプロジェクトのルート web.config ファイルに追加します。 以下のコードは、カルチャが米国英語に設定された globalization 要素を示しています。

<system.web>
    <globalization culture ="en-US" />
    <!--elements removed for clarity-->
  </system.web>

次のチュートリアルでは、検索機能を実装します。