パート 6: ASP.NET Core のコントローラーのメソッドとビュー

注

これは、この記事の最新バージョンではありません。 現在のリリースについては、 この記事の .NET 10 バージョンを参照してください。

警告

このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、 この記事の .NET 10 バージョンを参照してください。

ムービー アプリは良い出だしになっていますが、表示方法は理想的ではありません。たとえば、ReleaseDate は 2 つの単語にすべきです。

インデックス ビュー: リリース日は 1 単語 (スペースなし) です。

Models/Movie.cs ファイルを開き、下の画像で強調表示されている行を追加してください。

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string? Title { get; set; }
    
    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string? Genre { get; set; }
    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

DataAnnotations については、次のチュートリアルで説明します。 Display 属性は、フィールドの名前として表示する内容 (ここでは、"ReleaseDate" ではなく、"Release Date") を指定します。 DataType 属性は、データの型 (Date) を指定します。

上記のデータ注釈を使用して、アプリケーションを実行し、 /Movies ページを更新します。 ビュー マークアップでは、プロパティの名前と値をレンダリングするために @Html.DisplayNameFor メソッドと @Html.DisplayFor メソッドが使用されるため、更新された Index ビューには、適切に書式設定されたすべてのフィールドが表示されます。 たとえば、 リリース日 は 2 つの単語になりました。

インデックス ビュー: リリース日は 2 つの単語です。

[Column(TypeName = "decimal(18, 2)")] データ注釈は、Entity Framework Core が Price をデータベース内で通貨として正しくマッピングできるようにするために必要です。 詳細については、「Data Types」(データ型) を参照してください。

Movies コントローラーに移動し、[編集] リンクにマウス ポインターを合わせてターゲット URL を確認します。

[編集] リンクの上にマウスを置いたブラウザー ウィンドウが表示され、リンク URL がウィンドウに表示されます。

[編集]、[詳細]、[削除] の各リンクは、Views/Movies/Index.cshtml ファイル内の Core MVC アンカー タグ ヘルパーによって生成されます。

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。 上のコードでは、AnchorTagHelper によって、コントローラーのアクション メソッドとルート ID から HTML の href 属性値が動的に生成されます。お使いのブラウザーの ソースの表示 または開発者ツールを使用して、生成されたマークアップを確認できます。 生成された HTML の部分を以下に示します。

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Program.cs ファイルで設定したroutingの形式を思い出してください。

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}")
    .WithStaticAssets();

ASP.NET Core は、https://localhost:5001/Movies/Edit/4 を、パラメーター Id の値を 4 とした Edit コントローラーの Movies アクション メソッドへのリクエストに変換します。 (コントローラー メソッドはアクション メソッドとも呼ばれます)。

タグ ヘルパーは、ASP.NET Core で最もよく使われる新機能の 1 つです。 詳細については、「 関連コンテンツ」を参照してください。

Movies コントローラーを開き、2 つの Edit アクション メソッドを調べます。 次のコードは HTTP GET Edit メソッドを示しており、このメソッドはムービーをフェッチし、Edit.cshtmlEdit.cshtmlRazor ファイルによって生成される編集フォームにデータを設定します。

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

次のコードは、投稿されたムービーの値を処理する HTTP POST Edit メソッドを示します。

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

[Bind] 属性は、オーバーポスティング攻撃を防ぐための 1 つの方法です。 変更したいプロパティだけを [Bind] 属性に含める必要があります。 詳細については、オーバーポスティング攻撃からのコントローラーの保護に関する記事を参照してください。 ViewModels は、オーバーポスティング攻撃を防ぐもう 1 つの方法を提供します。

2 番目の Edit アクション メソッドの前に [HttpPost] 属性が付いていることに注意してください。

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

HttpPost 属性は、この Edit メソッドを POST リクエストに対してのみ呼び出せることを指定します。 1 番目の Edit メソッドにも [HttpGet] 属性を適用してもかまいませんが、[HttpGet] が既定値なので必要ありません。

ValidateAntiForgeryToken 属性は、要求の偽造を防ぐために使用され、編集ビュー ファイル (Views/Movies/Edit.cshtml) で生成される偽造防止トークンとペアになっています。 編集ビュー ファイルは、フォーム タグ ヘルパーを使用して偽造防止トークンを生成します。

<form asp-action="Edit">

Form Tag Helper は非表示の偽造防止トークンを生成します。このトークンは、Movies コントローラーの Edit メソッドで生成される [ValidateAntiForgeryToken] 偽造防止トークンと一致しなければなりません。 詳細については、「ASP.NET Core でクロスサイト リクエスト フォージェリ (XSRF/CSRF) 攻撃を防ぐ」を参照してください。

HttpGet Edit メソッドは、ムービーの ID パラメーターを受け取り、Entity Framework の FindAsync メソッドを使ってムービーを検索して、選択されたムービーを編集ビューに返します。 ムービーが見つからない場合は、NotFound (HTTP 404) が返されます。

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

スキャフォールディング システムが編集ビューを作成したときは、そのシステムが Movie クラスを調べて、クラスの各プロパティの <label> および <input> 要素をレンダリングするコードを作成しました。 次の例では、Visual Studio のスキャフォールディング システムによって生成された編集ビューを示します。

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

ビュー テンプレートではファイルの先頭に @model MvcMovie.Models.Movie ステートメントがあることに注意してください。 @model MvcMovie.Models.Movie は、ビューがビュー テンプレートのモデルとして Movie 型を期待することを指定します。

スキャフォールディングされたコードは、複数のタグ ヘルパー メソッドを使って HTML マークアップを効率化します。 ラベル タグ ヘルパーは、フィールドの名前を表示します ("Title"、"ReleaseDate"、"Genre"、"Price")。 入力タグ ヘルパーは、HTML の <input> 要素をレンダリングします。 検証タグ ヘルパーは、そのプロパティに関連付けられている検証メッセージを表示します。

アプリケーションを実行し、/Movies URL に移動します。 編集 リンクをクリックします。 ブラウザーで、ページのソースを表示します。 <form> 要素の生成された HTML を次に示します。

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

<input> 要素は、action 属性が /Movies/Edit/id URL に送信するように設定された HTML <form> 要素に含まれます。 [Save] ボタンがクリックされると、フォームのデータがサーバーに送信されます。 </form> 要素を閉じる前に最後の行では、フォーム タグ ヘルパーによって生成された非表示の XSRF トークンが示されています。

POSTリクエストの処理

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

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

[ValidateAntiForgeryToken] 属性は、フォーム タグ ヘルパーの偽造防止トークン ジェネレーターによって生成された非表示の XSRF トークンを検証します。

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

フォームがサーバーに送信される前に、クライアント側の検証はフィールドに対する検証規則を確認します。 検証エラーがある場合は、エラー メッセージが表示され、フォームは送信されません。 JavaScript が無効になっている場合、クライアント側検証は行われませんが、サーバーは送信された無効な値を検出し、フォーム値がエラー メッセージと共に再表示されます。 このチュートリアルで後ほど、モデルの検証についてさらに詳しく説明します。 Views/Movies/Edit.cshtml ビュー テンプレートのValidation Tag Helperは、適切なエラー メッセージの表示を処理します。

編集ビュー: abc の正しくない Price 値の例外は、フィールド Price が数値である必要があることを示します。リリース日の値が正しくない場合の例外は、有効な日付を入力してください。

Movie コントローラーのすべての HttpGet メソッドは、同様のパターンに従います。 ムービー オブジェクト (または、Index の場合はオブジェクトの一覧) を取得し、オブジェクト (モデル) をビューに渡します。 Create メソッドは、空のムービー オブジェクトを Create ビューに渡します。 データの作成、編集、削除、またはそれ以外の変更を行うすべてのメソッドは、メソッドの [HttpPost] オーバーロードによって実行されます。 HTTP GET メソッドでデータを変更すると、セキュリティ リスクがあります。 HTTP GET メソッドでデータを変更することは、HTTP のベスト プラクティスや、GET 要求ではアプリケーションの状態を変更してはならないというアーキテクチャの REST パターンにも、違反しています。 つまり、GET 操作の実行は、副作用がなく、永続化されたデータを変更しない、安全な操作である必要があります。

ムービー アプリは上々の滑り出しでしたが、表示が理想的ではありません。たとえば、ReleaseDate は 2 語でなければなりません。

インデックスビュー: Release Date は1語(スペースなし)で、すべての映画の公開日時に午前12時が表示される

Models/Movie.cs ファイルを開き、下に示す強調表示された行を追加してください。

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string? Title { get; set; }
    
    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string? Genre { get; set; }
    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

DataAnnotations については、次のチュートリアルで説明します。 Display 属性は、フィールドの名前として表示する内容 (ここでは、"Release Date" ではなく "ReleaseDate") を指定します。 DataType 属性はデータの型 (Date) を指定するため、フィールドに格納されている時刻情報は Date 型であるため表示されません。

上記のデータ注釈を使用して、アプリケーションを実行し、 /Movies ページを更新します。 ビュー マークアップでは、プロパティの名前と値をレンダリングするために @Html.DisplayNameFor メソッドと @Html.DisplayFor メソッドが使用されるため、更新された Index ビューには、適切に書式設定されたすべてのフィールドが表示されます。 たとえば、 リリース日 は 2 つの単語になり、時刻情報は表示されなくなります。

インデックス ビュー: リリース日は 2 つの単語で、すべての映画のリリース日は時刻なしで表示されます

[Column(TypeName = "decimal(18, 2)")] データ注釈は、Entity Framework Core が Price をデータベース内で通貨として正しくマッピングできるようにするために必要です。 詳細については、「Data Types」(データ型) を参照してください。

Movies コントローラーに移動し、[編集] リンクにマウス ポインターを合わせてターゲット URL を確認します。

[編集] リンクがマウスでポイントされ、リンク URL として https://localhost:5001/Movies/Edit/5 が表示されている状態のブラウザー ウィンドウ

[編集] 、 [詳細] 、 [削除] の各リンクは、Views/Movies/Index.cshtml ファイルで Core MVC アンカー タグ ヘルパーによって生成されます。

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードが関与できるようにします。 上記のコードでは、AnchorTagHelper によって、コントローラーのアクション メソッドとルート ID から HTML の href 属性値が動的に生成されます。お使いのブラウザーの ソースの表示 または開発者ツールを使用して、生成されたマークアップを調べることができます。 生成された HTML の部分を以下に示します。

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

routing の形式を Program.cs ファイルで設定することを思い出してください。

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core は、https://localhost:5001/Movies/Edit/4 を、パラメーター Id が 4 の Edit コントローラーの Movies アクション メソッドへの要求としてマップします。 (コントローラー メソッドはアクション メソッドとも呼ばれます)。

タグ ヘルパーは、ASP.NET Core で最もよく使われる新機能の 1 つです。 詳細については、「 関連コンテンツ」を参照してください。

Movies コントローラーを開き、2 つの Edit アクション メソッドを調べます。 次のコードは HTTP GET Edit メソッドを示しており、このメソッドはムービーをフェッチし、Edit.cshtmlEdit.cshtmlRazor ファイルによって生成される編集フォームを設定します。

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

次に示すコードの HTTP POST Edit メソッドは、送信されたムービーの値を処理します。

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

[Bind] 属性は、オーバーポスティング攻撃を防ぐための 1 つの方法です。 変更したいプロパティだけを [Bind] 属性に含めてください。 詳細については、オーバーポスティング攻撃からのコントローラーの保護に関する記事を参照してください。 ViewModels は、オーバーポスティング攻撃を防ぐもう 1 つの方法を提供します。

2 番目の Edit アクション メソッドの前に [HttpPost] 属性が付いていることに注意してください。

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

HttpPost 属性は、この Edit メソッドを POST 要求に対してのみ呼び出すことができることを指定します。 最初の Edit メソッドに [HttpGet] 属性を適用することもできますが、[HttpGet] が既定値であるため、その必要はありません。

ValidateAntiForgeryToken 属性は、要求の偽造を防ぐために使用され、編集ビュー ファイル (Views/Movies/Edit.cshtml) で生成される偽造防止トークンとペアになっています。 編集ビュー ファイルは、フォーム タグ ヘルパーを使用して偽造防止トークンを生成します。

<form asp-action="Edit">

フォーム タグ ヘルパーは非表示の偽造防止トークンを生成し、そのトークンは、Movies コントローラーの Edit メソッドで生成された [ValidateAntiForgeryToken] 偽造防止トークンと一致する必要があります。 詳細については、「ASP.NET Core でのクロスサイト リクエスト フォージェリ (XSRF/CSRF) 攻撃の防止」を参照してください。

HttpGet Edit メソッドは、ムービーの ID パラメーターを受け取り、Entity Framework の FindAsync メソッドを使ってムービーを検索して、選択されたムービーを編集ビューに返します。 ムービーが見つからない場合は、NotFound (HTTP 404) が返されます。

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

スキャフォールディング システムが編集ビューを作成したときは、そのシステムが Movie クラスを調べて、クラスの各プロパティの <label> および <input> 要素をレンダリングするコードを作成しました。 次の例は、Visual Studio のスキャフォールディング システムによって生成された [編集] ビューを表示しています。

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

ビュー テンプレートではファイルの先頭に @model MvcMovie.Models.Movie ステートメントがあることに注意してください。 @model MvcMovie.Models.Movie は、ビューがビュー テンプレートのモデルとして Movie 型を期待することを指定します。

スキャフォールディングされたコードは、複数のタグ ヘルパー メソッドを使って HTML マークアップを効率化します。 ラベル タグ ヘルパーは、フィールドの名前を表示します ("Title"、"ReleaseDate"、"Genre"、"Price")。 入力タグ ヘルパーは、HTML の <input> 要素をレンダリングします。 検証タグ ヘルパーは、そのプロパティに関連付けられている検証メッセージを表示します。

アプリケーションを実行し、/Movies URL にアクセスします。 Edit をクリックします。 ブラウザーでページのソースを表示する。 <form> 要素に対して生成された HTML を次に示します。

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

<input> 要素は、action 属性が /Movies/Edit/id URL への送信に設定された HTML <form> 要素に含まれます。 [Save] ボタンがクリックされると、フォームのデータがサーバーに送信されます。 </form> 要素を閉じる前の最後の行には、フォーム タグ ヘルパーによって生成された非表示の XSRF トークンが示されています。

POST 要求の処理

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

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

[ValidateAntiForgeryToken] 属性では、Form Tag Helper の偽造防止トークン ジェネレーターによって生成された非表示の XSRF トークンを検証します。

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

フォームがサーバーに送信される前に、クライアント側で各フィールドに設定された検証ルールが確認されます。 検証エラーが発生した場合は、エラーメッセージが表示され、フォームは送信されません。 JavaScript が無効になっている場合、クライアント側の検証は利用できませんが、サーバーは送信された無効な値を検出し、フォームの値はエラーメッセージとともに再表示されます。 このチュートリアルで後ほど、モデルの検証についてさらに詳しく説明します。 Views/Movies/Edit.cshtml ビュー テンプレートの Validation Tag Helper は、適切なエラー メッセージの表示を処理します。

編集ビュー: 正しくない価格の値 abc に対する例外では、

Movie コントローラーのすべての HttpGet メソッドは、同様のパターンに従います。 ムービー オブジェクト (または、Index の場合はオブジェクトの一覧) を取得し、オブジェクト (モデル) をビューに渡します。 Create メソッドは、空のムービー オブジェクトを Create ビューに渡します。 データの作成、編集、削除、またはそれ以外の変更を行うすべてのメソッドは、メソッドの [HttpPost] オーバーロードで行われます。 HTTP GET メソッドでデータを変更すると、セキュリティ リスクがあります。 HTTP GET メソッドでデータを変更することは、HTTP のベスト プラクティスや、GET 要求ではアプリケーションの状態を変更してはならないというアーキテクチャの REST パターンにも、違反しています。 つまり、GET 操作の実行は、副作用がなく、永続化されたデータを変更しない、安全な操作である必要があります。

ムービー アプリは上々の滑り出しでしたが、表示が理想的ではありません。たとえば、ReleaseDate は 2 語でなければなりません。

インデックス ビュー:Release Date が 1 語 (スペースなし) で、ムービーの公開日がすべて午前 12 時になっています

Models/Movie.cs ファイルを開き、下に示す強調表示された行を追加してください。

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string? Title { get; set; }
    
    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string? Genre { get; set; }
    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

DataAnnotations については、次のチュートリアルで説明します。 Display 属性は、フィールドの名前として表示する内容 (ここでは、"Release Date" ではなく "ReleaseDate") を指定します。 DataType 属性はデータ型 (Date) を指定するため、フィールドに格納されている時刻情報は表示されません。

[Column(TypeName = "decimal(18, 2)")] データ注釈は、Entity Framework Core が Price をデータベース内で通貨として正しくマッピングできるようにするために必要です。 詳細については、「Data Types」(データ型) を参照してください。

Movies コントローラーに移動し、[編集] リンクにマウス ポインターを合わせてターゲット URL を確認します。

[編集] リンクにマウス ポインターが重ねられ、リンク URL が https://localhost:5001/Movies/Edit/5 と表示されたブラウザー ウィンドウが表示されます

[編集] 、 [詳細] 、 [削除] の各リンクは、Views/Movies/Index.cshtml ファイルで Core MVC アンカー タグ ヘルパーによって生成されます。

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードが関与できるようにします。 上記のコードでは、AnchorTagHelper によって、コントローラーのアクション メソッドとルート ID から HTML の href 属性値が動的に生成されます。お使いのブラウザーの ソースの表示 または開発者ツールを使用して、生成されたマークアップを調べることができます。 生成された HTML の部分を以下に示します。

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

routing の形式を Program.cs ファイルで設定することを思い出してください。

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core は、https://localhost:5001/Movies/Edit/4 を、パラメーター Id が 4 の Edit コントローラーの Movies アクション メソッドへの要求としてマップします。 (コントローラー メソッドはアクション メソッドとも呼ばれます)。

タグ ヘルパーは、ASP.NET Core の新機能の中でも特に人気のある機能の 1 つです。 詳細については、「 関連コンテンツ」を参照してください。

Movies コントローラーを開き、2 つの Edit アクション メソッドを調べます。 次のコードは HTTP GET Edit メソッドを示しており、このメソッドはムービーをフェッチし、Edit.cshtmlEdit.cshtmlRazor ファイルによって生成される編集フォームを設定します。

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

次に示すコードの HTTP POST Edit メソッドは、送信されたムービーの値を処理します。

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

[Bind] 属性は、オーバーポスティング攻撃を防ぐための 1 つの方法です。 変更したいプロパティだけを [Bind] 属性に含めてください。 詳細については、オーバーポスティング攻撃からのコントローラーの保護に関する記事を参照してください。 ViewModels は、オーバーポスティングを防ぐもう 1 つの方法を提供します。

2 番目の Edit アクション メソッドの前に [HttpPost] 属性が付いていることに注意してください。

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

HttpPost 属性は、この Edit メソッドを POST 要求に対してのみ呼び出すことができることを指定します。 最初の Edit メソッドに [HttpGet] 属性を適用することもできますが、[HttpGet] が既定値であるため、その必要はありません。

ValidateAntiForgeryToken 属性は、要求の偽造を防ぐために使用され、編集ビュー ファイル (Views/Movies/Edit.cshtml) で生成される偽造防止トークンとペアになっています。 編集ビュー ファイルは、フォーム タグ ヘルパーを使用して偽造防止トークンを生成します。

<form asp-action="Edit">

フォーム タグ ヘルパーは非表示の偽造防止トークンを生成し、そのトークンは、Movies コントローラーの Edit メソッドで生成された [ValidateAntiForgeryToken] 偽造防止トークンと一致する必要があります。 詳細については、「ASP.NET Core におけるクロスサイト リクエスト フォージェリ (XSRF/CSRF) 攻撃の防止」を参照してください。

HttpGet Edit メソッドは、ムービーの ID パラメーターを受け取り、Entity Framework の FindAsync メソッドを使ってムービーを検索して、選択されたムービーを編集ビューに返します。 ムービーが見つからない場合は、NotFound (HTTP 404) が返されます。

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

スキャフォールディング システムが編集ビューを作成したときは、そのシステムが Movie クラスを調べて、クラスの各プロパティの <label> および <input> 要素をレンダリングするコードを作成しました。 次の例では、Visual Studio のスキャフォールディング システムによって生成された編集ビューを示します。

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

ビュー テンプレートではファイルの先頭に @model MvcMovie.Models.Movie ステートメントがあることに注意してください。 @model MvcMovie.Models.Movie は、ビューがビュー テンプレートのモデルとして Movie 型を期待することを指定します。

スキャフォールディングされたコードは、複数のタグ ヘルパー メソッドを使って HTML マークアップを効率化します。 ラベル タグ ヘルパーは、フィールドの名前を表示します ("Title"、"ReleaseDate"、"Genre"、"Price")。 Input Tag Helper は HTML の <input> 要素を生成します。 検証タグ ヘルパーは、そのプロパティに関連付けられている検証メッセージを表示します。

アプリケーションを実行し、/Movies URL にアクセスします。 編集 リンクをクリックします。 ブラウザーでページのソースコードを表示します。 <form> 要素に対して生成された HTML を次に示します。

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

<input> 要素は、action 属性が /Movies/Edit/id URL に POST するよう設定された HTML <form> 要素内にあります。 [Save] ボタンがクリックされると、フォームのデータがサーバーに送信されます。 </form> 要素を閉じる前の最後の行では、フォーム タグ ヘルパーによって生成された非表示の XSRF トークンが示されています。

POST 要求の処理

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

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

[ValidateAntiForgeryToken] 属性は、Form Tag Helper 内の偽造防止トークン ジェネレーターによって生成された非表示の XSRF トークンを検証します。

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

フォームがサーバーに送信される前に、クライアント側の検証はフィールドに対する検証規則をチェックします。 検証エラーがある場合は、エラー メッセージが表示され、フォームは送信されません。 JavaScript が無効になっている場合、クライアント側検証は行われませんが、サーバーは送信された無効な値を検出し、フォーム値がエラー メッセージと共に再表示されます。 このチュートリアルで後ほど、モデルの検証についてさらに詳しく説明します。 Views/Movies/Edit.cshtml ビュー テンプレートの Validation Tag Helper は、適切なエラー メッセージの表示を処理します。

編集ビュー: 正しくない価格の値 abc に対する例外では、

Movie コントローラーのすべての HttpGet メソッドは、同様のパターンに従います。 ムービー オブジェクト (または、Index の場合はオブジェクトの一覧) を取得し、オブジェクト (モデル) をビューに渡します。 Create メソッドは、空のムービー オブジェクトを Create ビューに渡します。 データの作成、編集、削除、またはそれ以外の変更を行うすべてのメソッドは、メソッドの [HttpPost] オーバーロードで行われます。 HTTP GET メソッドでデータを変更すると、セキュリティ リスクがあります。 HTTP GET メソッドでデータを変更することは、HTTP のベスト プラクティスや、GET 要求ではアプリケーションの状態を変更してはならないというアーキテクチャの REST パターンにも、違反しています。 つまり、GET 操作の実行は、副作用がなく、永続化されたデータを変更しない、安全な操作である必要があります。

ムービー アプリは良い出だしになっていますが、表示方法は理想的ではありません。たとえば、ReleaseDate は 2 つの単語にすべきです。

インデックス ビュー:Release Date が 1 語 (スペースなし) で、ムービーの公開日がすべて午前 12 時になっています

Models/Movie.cs ファイルを開き、下の画像で強調表示されている行を追加してください。

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcMovie.Models;

public class Movie
{
    public int Id { get; set; }
    public string? Title { get; set; }
    
    [Display(Name = "Release Date")]
    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
    public string? Genre { get; set; }
    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }
}

DataAnnotations については、次のチュートリアルで説明します。 Display 属性は、フィールドの名前として表示する内容 (ここでは、"ReleaseDate" ではなく、"Release Date") を指定します。 DataType 属性はデータ型 (Date) を指定するため、フィールドに格納される時刻情報は表示されません。

[Column(TypeName = "decimal(18, 2)")] データ注釈は、Entity Framework Core が Price をデータベース内で通貨として正しくマッピングできるようにするために必要です。 詳細については、「Data Types」(データ型) を参照してください。

Movies コントローラーに移動し、[編集] リンクにマウス ポインターを合わせてターゲット URL を確認します。

[編集] リンクがマウスでポイントされ、リンク URL として https://localhost:5001/Movies/Edit/5 が表示されている状態のブラウザー ウィンドウ

[編集]、[詳細]、[削除] の各リンクは、Views/Movies/Index.cshtml ファイル内の Core MVC アンカー タグ ヘルパーによって生成されます。

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。 上のコードでは、AnchorTagHelper によって、コントローラーのアクション メソッドとルート ID から HTML の href 属性値が動的に生成されます。お使いのブラウザーの ソースの表示 または開発者ツールを使用して、生成されたマークアップを確認できます。 生成された HTML の部分を以下に示します。

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Program.cs ファイルで設定したroutingの形式を思い出してください。

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core は、https://localhost:5001/Movies/Edit/4 を、パラメーター Id の値を 4 とした Edit コントローラーの Movies アクション メソッドへのリクエストに変換します。 (コントローラー メソッドはアクション メソッドとも呼ばれます)。

タグ ヘルパーは、ASP.NET Core で最もよく使われる新機能の 1 つです。 詳細については、「 関連コンテンツ」を参照してください。

Movies コントローラーを開き、2 つの Edit アクション メソッドを調べます。 次のコードは HTTP GET Edit メソッドを示しており、このメソッドはムービーをフェッチし、Edit.cshtmlEdit.cshtmlRazor ファイルによって生成される編集フォームにデータを設定します。

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

次のコードは、投稿されたムービーの値を処理する HTTP POST Edit メソッドを示します。

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

[Bind] 属性は、オーバーポスティング攻撃を防ぐための 1 つの方法です。 変更したいプロパティだけを [Bind] 属性に含める必要があります。 詳細については、オーバーポスティング攻撃からのコントローラーの保護に関する記事を参照してください。 ViewModels は、オーバーポスティング攻撃を防ぐもう 1 つの方法を提供します。

2 番目の Edit アクション メソッドの前に [HttpPost] 属性が付いていることに注意してください。

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

HttpPost 属性は、この Edit メソッドを POST リクエストに対してのみ呼び出せることを指定します。 1 番目の Edit メソッドにも [HttpGet] 属性を適用してもかまいませんが、[HttpGet] が既定値なので必要ありません。

ValidateAntiForgeryToken 属性は、要求の偽造を防ぐために使用され、編集ビュー ファイル (Views/Movies/Edit.cshtml) で生成される偽造防止トークンとペアになっています。 編集ビュー ファイルは、フォーム タグ ヘルパーを使用して偽造防止トークンを生成します。

<form asp-action="Edit">

Form Tag Helper は非表示の偽造防止トークンを生成します。このトークンは、Movies コントローラーの Edit メソッドで生成される [ValidateAntiForgeryToken] 偽造防止トークンと一致しなければなりません。 詳細については、「ASP.NET Core でクロスサイト リクエスト フォージェリ (XSRF/CSRF) 攻撃を防ぐ」を参照してください。

HttpGet Edit メソッドは、ムービーの ID パラメーターを受け取り、Entity Framework の FindAsync メソッドを使ってムービーを検索して、選択されたムービーを編集ビューに返します。 ムービーが見つからない場合は、NotFound (HTTP 404) が返されます。

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

スキャフォールディング システムが編集ビューを作成したときは、そのシステムが Movie クラスを調べて、クラスの各プロパティの <label> および <input> 要素をレンダリングするコードを作成しました。 次の例では、Visual Studio のスキャフォールディング システムによって生成された編集ビューを示します。

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

ビュー テンプレートではファイルの先頭に @model MvcMovie.Models.Movie ステートメントがあることに注意してください。 @model MvcMovie.Models.Movie は、ビューがビュー テンプレートのモデルとして Movie 型を期待することを指定します。

スキャフォールディングされたコードは、複数のタグ ヘルパー メソッドを使って HTML マークアップを効率化します。 ラベル タグ ヘルパーは、フィールドの名前を表示します ("Title"、"ReleaseDate"、"Genre"、"Price")。 入力タグ ヘルパーは、HTML の <input> 要素をレンダリングします。 検証タグ ヘルパーは、そのプロパティに関連付けられている検証メッセージを表示します。

アプリケーションを実行し、/Movies URL に移動します。 編集 リンクをクリックします。 ブラウザーでページのソースを表示します。 <form> 要素に対して生成された HTML を次に示します。

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

<input> 要素は、HTML <form> URL に送信するように action 属性が設定された /Movies/Edit/id 要素に含まれます。 [Save] ボタンがクリックされると、フォームのデータがサーバーに送信されます。 閉じる </form> 要素の直前の行には、Form Tag Helper によって生成された非表示の XSRF トークンが表示されています。

POST 要求の処理

次のコード一覧は、Edit アクション メソッドの [HttpPost] バージョンを示しています。

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

[ValidateAntiForgeryToken] 属性は、フォーム タグ ヘルパーの偽造防止トークン ジェネレーターによって生成された非表示の XSRF トークンを検証します。

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

フォームがサーバーに送信される前に、クライアント側の検証では、フィールドの入力値が検証ルールに従っているかどうかを確認します。 検証エラーがある場合は、エラーメッセージが表示され、フォームは送信されません。 JavaScript が無効になっている場合、クライアント側の検証は行われませんが、サーバーは送信された値のうち無効なものを検出し、フォームの値はエラーメッセージと共に再表示されます。 このチュートリアルで後ほど、モデルの検証についてさらに詳しく説明します。 Views/Movies/Edit.cshtml ビュー テンプレートの 検証タグ ヘルパー は、適切なエラー メッセージを表示します。

編集ビュー: 正しくない価格の値 abc に対する例外では、

Movie コントローラーのすべての HttpGet メソッドは、同様のパターンに従います。 ムービー オブジェクト (または、Index の場合はオブジェクトの一覧) を取得し、オブジェクト (モデル) をビューに渡します。 Create メソッドは、空のムービー オブジェクトを Create ビューに渡します。 データの作成、編集、削除、またはそれ以外の変更を行うすべてのメソッドは、そのメソッドの [HttpPost] オーバーロードでそれを行います。 HTTP GET メソッドでデータを変更すると、セキュリティ リスクがあります。 HTTP GET メソッドでデータを変更することは、HTTP のベスト プラクティスや、GET 要求ではアプリケーションの状態を変更してはならないというアーキテクチャの REST パターンにも、違反しています。 つまり、GET 操作の実行は、副作用がなく、永続化されたデータを変更しない、安全な操作である必要があります。

ムービー アプリは良い出だしになっていますが、表示方法は理想的ではありません。たとえば、ReleaseDate は 2 つの単語にすべきです。

インデックス ビュー:Release Date が 1 語 (スペースなし) で、ムービーの公開日がすべて午前 12 時になっています

Models/Movie.cs ファイルを開き、下の画像で強調表示されている行を追加してください。

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

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

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string? Genre { get; set; }

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

DataAnnotations については、次のチュートリアルで説明します。 Display 属性は、フィールドの名前として表示する内容 (ここでは、"ReleaseDate" ではなく、"Release Date") を指定します。 DataType 属性はデータ型 (Date) を指定するため、フィールドに格納される時刻情報は表示されません。

[Column(TypeName = "decimal(18, 2)")] データ注釈は、Entity Framework Core が Price をデータベース内で通貨として正しくマッピングできるようにするために必要です。 詳細については、「Data Types」(データ型) を参照してください。

Movies コントローラーに移動し、[編集] リンクにマウス ポインターを合わせてターゲット URL を確認します。

[編集] リンクにマウス ポインターを合わせ、リンク URL として https://localhost:5001/Movies/Edit/5 が表示されている状態のブラウザー ウィンドウ

[編集]、[詳細]、[削除] の各リンクは、Views/Movies/Index.cshtml ファイル内の Core MVC アンカー タグ ヘルパーによって生成されます。

        <a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.Id">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
    </td>
</tr>

タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。 上のコードでは、AnchorTagHelper によって、コントローラーのアクション メソッドとルート ID から HTML の href 属性値が動的に生成されます。お使いのブラウザーの ソースの表示 または開発者ツールを使用して、生成されたマークアップを確認できます。 生成された HTML の部分を以下に示します。

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Program.cs ファイルで設定したroutingの形式を思い出してください。

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

ASP.NET Core は、https://localhost:5001/Movies/Edit/4 を、パラメーター Id の値を 4 とした Edit コントローラーの Movies アクション メソッドへのリクエストに変換します。 (コントローラー メソッドはアクション メソッドとも呼ばれます)。

タグ ヘルパーは、ASP.NET Core の人気機能です。 詳細については、「 関連コンテンツ」を参照してください。

Movies コントローラーを開き、2 つの Edit アクション メソッドを調べます。 次のコードは HTTP GET Edit メソッドを示しており、ムービーがフェッチされ、Edit.cshtmlEdit.cshtmlRazor ファイルによって生成される編集フォームが設定されます。

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

次に示すコードの HTTP POST Edit メソッドは、送信されたムービーの値を処理します。

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

[Bind] 属性は、オーバーポスティング攻撃を防ぐための 1 つの方法です。 変更する [Bind] 属性にだけプロパティを含める必要があります。 詳細については、オーバーポスティングからコントローラーを保護するに関する記事を参照してください。 ViewModels は、オーバーポスティングを防ぐための別のアプローチになります。

2番目の Edit アクション メソッドには、その前に [HttpPost] 属性が付与されていることに注意してください。

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

HttpPost 属性は、この Edit メソッドを POST 要求に対してのみ呼び出せることを指定します。 1 番目の Edit メソッドにも [HttpGet] 属性を適用してもかまいませんが、[HttpGet] が既定値なので必要ありません。

ValidateAntiForgeryToken 属性は、要求の偽造を防ぐために使用され、編集ビュー ファイル (Views/Movies/Edit.cshtml) で生成される偽造防止トークンとペアになっています。 編集ビュー ファイルでは、Form Tag Helper を使用して偽造防止トークンを生成します。

<form asp-action="Edit">

フォーム タグ ヘルパーは非表示の偽造防止トークンを生成し、そのトークンは、Movies コントローラーの [ValidateAntiForgeryToken] メソッドで生成された偽造防止トークンEdit と一致する必要があります。 詳細については、「ASP.NET Core でのクロスサイト リクエスト フォージェリ (XSRF/CSRF) 攻撃の防止」を参照してください。

HttpGet Edit メソッドは、ムービーの ID パラメーターを受け取り、Entity Framework の FindAsync メソッドを使ってムービーを検索して、選択されたムービーを編集ビューに返します。 ムービーが見つからない場合は、NotFound (HTTP 404) が返されます。

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

スキャフォールディング システムが編集ビューを作成したときは、そのシステムが Movie クラスを調べて、クラスの各プロパティの <label> および <input> 要素をレンダリングするコードを作成しました。 次の例では、Visual Studio のスキャフォールディング システムによって生成された編集ビューを示します。

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

ビュー テンプレートのファイル先頭に @model MvcMovie.Models.Movie ステートメントがある点に注目してください。 @model MvcMovie.Models.Movie は、ビュー テンプレート用のモデルが Movie 型であることを指定します。

スキャフォールディングされたコードは、複数のタグ ヘルパー メソッドを使って HTML マークアップを効率化します。 Label Tag Helper は、フィールド名("Title"、"ReleaseDate"、"Genre"、または "Price")を表示します。 入力タグ ヘルパーは、HTML の <input> 要素をレンダリングします。 Validation Tag Helper は、そのプロパティに関連付けられた検証メッセージを表示します。

アプリケーションを実行し、/Movies URL にアクセスします。 編集 リンクをクリックします。 ブラウザーでページのソースを表示します。 <form> 要素に対して生成された HTML を次に示します。

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

<input> 要素は、HTML <form> URL に送信するように action 属性が設定された /Movies/Edit/id 要素に含まれます。 [Save] ボタンがクリックされると、フォームのデータがサーバーに送信されます。 閉じる </form> 要素の直前の行には、Form Tag Helper によって生成された非表示の XSRF トークンが表示されています。

POST 要求の処理

次のコード一覧は、Edit アクション メソッドの [HttpPost] バージョンを示しています。

// POST: Movies/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,ReleaseDate,Genre,Price,Rating")] Movie movie)
{
    if (id != movie.Id)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

[ValidateAntiForgeryToken] 属性は、フォーム タグ ヘルパーの偽造防止トークン ジェネレーターによって生成された非表示の XSRF トークンを検証します。

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

フォームがサーバーに送信される前に、クライアント側の検証では、フィールドの入力値が検証ルールに従っているかどうかを確認します。 検証エラーがある場合は、エラーメッセージが表示され、フォームは送信されません。 JavaScript が無効になっている場合、クライアント側の検証は行われませんが、サーバーは送信された値のうち無効なものを検出し、フォームの値はエラーメッセージと共に再表示されます。 このチュートリアルで後ほど、モデルの検証についてさらに詳しく説明します。 Views/Movies/Edit.cshtml ビュー テンプレートの 検証タグ ヘルパー は、適切なエラー メッセージを表示します。

編集ビュー: 正しくない価格の値 abc に対する例外では、

Movie コントローラーのすべての HttpGet メソッドは、同様のパターンに従います。 ムービー オブジェクト (または、Index の場合はオブジェクトの一覧) を取得し、オブジェクト (モデル) をビューに渡します。 Create メソッドは、空のムービー オブジェクトを Create ビューに渡します。 データの作成、編集、削除、またはそれ以外の変更を行うすべてのメソッドは、そのメソッドの [HttpPost] オーバーロードで行われます。 HTTP GET メソッドでデータを変更すると、セキュリティ リスクがあります。 HTTP GET メソッドでデータを変更することは、HTTP のベスト プラクティスだけでなく、GET リクエストではアプリケーションの状態を変更してはならないと定めるアーキテクチャ REST パターンにも違反しています。 つまり、GET 操作の実行は、副作用がなく、永続化されたデータを変更しない、安全な操作である必要があります。

ムービー アプリは上々の滑り出しでしたが、表示が理想的ではありません。たとえば、ReleaseDate は 2 語でなければなりません。

インデックス ビュー:Release Date が 1 語 (スペースなし) で、ムービーの公開日がすべて午前 12 時になっています

Models/Movie.cs ファイルを開き、下の画像で強調表示されている行を追加してください。

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

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

        [Display(Name = "Release Date")]
        [DataType(DataType.Date)]
        public DateTime ReleaseDate { get; set; }
        public string Genre { get; set; }

        [Column(TypeName = "decimal(18, 2)")]
        public decimal Price { get; set; }
    }
}

DataAnnotations については、次のチュートリアルで説明します。 Display 属性は、フィールドの名前として表示する内容 (ここでは、"ReleaseDate" ではなく、"Release Date") を指定します。 DataType 属性はデータ型 (Date) を指定するため、フィールドに格納される時刻情報は表示されません。

[Column(TypeName = "decimal(18, 2)")] データ注釈は、Entity Framework Core がデータベースの通貨と Price を正しくマッピングできるようにするために必要です。 詳細については、「Data Types」(データ型) を参照してください。

Movies コントローラーを開き、[編集] リンクにマウス ポインターを合わせてターゲット URL を確認します。

[編集] リンクがマウスでポイントされ、リンク URL として https://localhost:5001/Movies/Edit/5 が表示されている状態のブラウザー ウィンドウ

編集、詳細、削除 の各リンクは、Views/Movies/Index.cshtml ファイル内で Core MVC アンカー タグ ヘルパーによって生成されます。

        <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
        <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
        <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
    </td>
</tr>

タグ ヘルパーを使うと、Razor ファイルでの HTML 要素の作成とレンダリングに、サーバー側コードを組み込むことができます。 上のコードでは、AnchorTagHelper はコントローラーのアクション メソッドとルート ID から HTML の href 属性の値を動的に生成します。好みのブラウザーの [ソースの表示] または開発者ツールを使って、生成されたマークアップを確認してください。 生成された HTML の部分を以下に示します。

 <td>
    <a href="/Movies/Edit/4"> Edit </a> |
    <a href="/Movies/Details/4"> Details </a> |
    <a href="/Movies/Delete/4"> Delete </a>
</td>

Startup.csファイルで設定されたroutingの形式を思い出してください。

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

ASP.NET Core は、https://localhost:5001/Movies/Edit/4 を、パラメーター Id が 4 である Edit コントローラーの Movies アクション メソッドへの要求として処理します (コントローラー メソッドはアクション メソッドとも呼ばれます)。

タグ ヘルパーの詳細については、「関連コンテンツ」を参照してください。

Movies コントローラーを開き、2 つの Edit アクション メソッドを調べます。 次のコードは HTTP GET Edit メソッドを示しています。このメソッドはムービーをフェッチし、Edit.cshtmlEdit.cshtmlRazor ファイルによって生成される編集フォームにデータを設定します。

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

次に示すコードの HTTP POST Edit メソッドは、送信されたムービーの値を処理します。

// POST: Movies/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for 
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (id != movie.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction("Index");
    }
    return View(movie);
}

[Bind] 属性は、オーバーポスティングを防止する 1 つの方法です。 変更したいプロパティだけを [Bind] 属性に含めてください。 詳細については、コントローラをオーバーポスティングから保護するを参照してください。 ViewModels は、オーバーポスティング攻撃を防ぐもう 1 つの方法を提供します。

2 番目の Edit アクション メソッドの前に [HttpPost] 属性が付いていることに注意してください。

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (id != movie.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

HttpPost 属性は、この Edit メソッドを POST 要求に対してのみ呼び出すことができることを指定します。 1 番目の Edit メソッドにも [HttpGet] 属性を適用してもかまいませんが、[HttpGet] が既定値なので必要ありません。

ValidateAntiForgeryToken 属性は、要求の偽造を防ぐために使用され、編集ビュー ファイル (Views/Movies/Edit.cshtml) で生成される偽造防止トークンとペアになっています。 編集ビューのファイルは、フォーム タグ ヘルパーを使用して偽造防止トークンを生成します。

<form asp-action="Edit">

フォーム タグ ヘルパーは非表示の偽造防止トークンを生成し、そのトークンは、Movies コントローラーの Edit メソッドで生成された [ValidateAntiForgeryToken] 偽造防止トークンと一致する必要があります。 詳細については、「ASP.NET Core でのクロスサイト リクエスト フォージェリ (XSRF/CSRF) 攻撃の防止」を参照してください。

HttpGet Edit メソッドは、ムービーの ID パラメーターを受け取り、Entity Framework の FindAsync メソッドを使ってムービーを検索して、選択されたムービーを編集ビューに返します。 ムービーが見つからない場合は、NotFound (HTTP 404) が返されます。

// GET: Movies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
    if (id == null)
    {
        return NotFound();
    }

    var movie = await _context.Movie.FindAsync(id);
    if (movie == null)
    {
        return NotFound();
    }
    return View(movie);
}

スキャフォールディング システムは Edit ビューを作成する際に Movie クラスを調べ、そのクラスの各プロパティに対して <label> 要素と <input> 要素をレンダリングするコードを作成しました。 次の例では、Visual Studio のスキャフォールディング システムによって生成された編集ビューを示します。

@model MvcMovie.Models.Movie

@{
    ViewData["Title"] = "Edit";
}

<h1>Edit</h1>

<h4>Movie</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Id" />
            <div class="form-group">
                <label asp-for="Title" class="control-label"></label>
                <input asp-for="Title" class="form-control" />
                <span asp-validation-for="Title" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ReleaseDate" class="control-label"></label>
                <input asp-for="ReleaseDate" class="form-control" />
                <span asp-validation-for="ReleaseDate" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Genre" class="control-label"></label>
                <input asp-for="Genre" class="form-control" />
                <span asp-validation-for="Genre" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" />
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

ビュー テンプレートではファイルの先頭に @model MvcMovie.Models.Movie ステートメントがあることに注意してください。 @model MvcMovie.Models.Movie は、ビューがビュー テンプレートのモデルとして Movie 型を期待することを指定します。

スキャフォールディングされたコードは、複数のタグ ヘルパー メソッドを使って HTML マークアップを効率化します。 ラベル タグ ヘルパーは、フィールドの名前を表示します ("Title"、"ReleaseDate"、"Genre"、"Price")。 Input Tag Helper は、HTML の <input> 要素を生成します。 検証タグ ヘルパーは、そのプロパティに関連付けられている検証メッセージを表示します。

アプリケーションを実行し、/Movies URL にアクセスします。 編集 リンクをクリックします。 ブラウザーでページのソースを表示します。 <form> 要素に対して生成された HTML を次に示します。

<form action="/Movies/Edit/7" method="post">
    <div class="form-horizontal">
        <h4>Movie</h4>
        <hr />
        <div class="text-danger" />
        <input type="hidden" data-val="true" data-val-required="The ID field is required." id="ID" name="ID" value="7" />
        <div class="form-group">
            <label class="control-label col-md-2" for="Genre" />
            <div class="col-md-10">
                <input class="form-control" type="text" id="Genre" name="Genre" value="Western" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Genre" data-valmsg-replace="true"></span>
            </div>
        </div>
        <div class="form-group">
            <label class="control-label col-md-2" for="Price" />
            <div class="col-md-10">
                <input class="form-control" type="text" 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" value="3.99" />
                <span class="text-danger field-validation-valid" data-valmsg-for="Price" data-valmsg-replace="true"></span>
            </div>
        </div>
        <!-- Markup 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>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8Inyxgp63fRFqUePGvuI5jGZsloJu1L7X9le1gy7NCIlSduCRx9jDQClrV9pOTTmqUyXnJBXhmrjcUVDJyDUMm7-MF_9rK8aAZdRdlOri7FmKVkRe_2v5LIHGKFcTjPrWPYnc9AdSbomkiOSaTEg7RU" />
</form>

<input> 要素は、action 属性が /Movies/Edit/id URL に送信するように設定された HTML <form> 要素に含まれています。 [Save] ボタンがクリックされると、フォームのデータがサーバーに送信されます。 </form> 要素を閉じる前の最後の行には、フォーム タグ ヘルパーによって生成された非表示の XSRF トークンが示されています。

POST 要求の処理

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

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("ID,Title,ReleaseDate,Genre,Price")] Movie movie)
{
    if (id != movie.ID)
    {
        return NotFound();
    }

    if (ModelState.IsValid)
    {
        try
        {
            _context.Update(movie);
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MovieExists(movie.ID))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }
        return RedirectToAction(nameof(Index));
    }
    return View(movie);
}

[ValidateAntiForgeryToken] 属性は、フォーム タグ ヘルパーの偽造防止トークン ジェネレーターによって生成された非表示の XSRF トークンを検証します。

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

フォームがサーバーに送信される前に、クライアント側の検証はフィールドに対する検証規則を確認します。 検証エラーがある場合は、エラー メッセージが表示され、フォームは送信されません。 JavaScript が無効になっている場合、クライアント側検証は行われませんが、サーバーは送信された無効な値を検出し、フォーム値がエラー メッセージと共に再表示されます。 このチュートリアルで後ほど、モデルの検証についてさらに詳しく説明します。 Validation Tag Helper は、Views/Movies/Edit.cshtml ビュー テンプレートで適切なエラー メッセージの表示を処理します。

編集ビュー: 正しくない価格の値 abc に対する例外では、

Movie コントローラーのすべての HttpGet メソッドは、同様のパターンに従います。 ムービー オブジェクト (または、Index の場合はオブジェクトの一覧) を取得し、オブジェクト (モデル) をビューに渡します。 Create メソッドは、空のムービー オブジェクトを Create ビューに渡します。 データの作成、編集、削除、またはそれ以外の変更を行うすべてのメソッドは、[HttpPost] を含むそのメソッドのオーバーロードで行われます。 HTTP GET メソッドでデータを変更すると、セキュリティ リスクがあります。 HTTP GET メソッドでデータを変更することは、HTTP のベスト プラクティスや、GET 要求ではアプリケーションの状態を変更してはならないというアーキテクチャの REST パターンにも、違反しています。 つまり、GET 操作は、副作用がなく、永続データを変更しない安全な操作であるべきです。