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

Note

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

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

作成者: Rick Anderson

ムービー アプリは上々の滑り出しでしたが、表示が理想的ではありません。たとえば、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 ファイルで設定するルーティングの形式を思い出してください。

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

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

タグ ヘルパーは、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 属性は、POST 要求に対して "のみ" この Edit メソッドを呼び出すことができることを指定します。 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);
}

スキャフォールディング システムが編集ビューを作成したときは、そのシステムが 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> 要素は、/Movies/Edit/id URL に送信するように action 属性が設定された 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 ビュー テンプレートの検証タグ ヘルパーは、適切なエラー メッセージの表示を処理します。

編集ビュー: 正しくない価格の値 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 ファイルで設定するルーティングの形式を思い出してください。

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

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

タグ ヘルパーは、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 属性は、POST 要求に対して "のみ" この Edit メソッドを呼び出すことができることを指定します。 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);
}

スキャフォールディング システムが編集ビューを作成したときは、そのシステムが 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> 要素は、/Movies/Edit/id URL に送信するように action 属性が設定された 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 ビュー テンプレートの検証タグ ヘルパーは、適切なエラー メッセージの表示を処理します。

編集ビュー: 正しくない価格の値 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 ファイルで設定するルーティングの形式を思い出してください。

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

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

タグ ヘルパーは、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 は、オーバーポスティング攻撃を防ぐもう 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 属性は、POST 要求に対して "のみ" この Edit メソッドを呼び出すことができることを指定します。 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);
}

スキャフォールディング システムが編集ビューを作成したときは、そのシステムが 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> 要素は、/Movies/Edit/id URL に送信するように action 属性が設定された 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 ビュー テンプレートの検証タグ ヘルパーは、適切なエラー メッセージの表示を処理します。

編集ビュー: 正しくない価格の値 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 ファイルで設定するルーティングの形式を思い出してください。

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

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

タグ ヘルパーの詳細については、「その他の技術情報」を参照してください。

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 属性は、POST 要求に対して "のみ" この Edit メソッドを呼び出すことができることを指定します。 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);
}

スキャフォールディング システムが編集ビューを作成したときは、そのシステムが 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> 要素は、/Movies/Edit/id URL に送信するように action 属性が設定された HTML <form> 要素に含まれます。 [Save] ボタンがクリックされると、フォームのデータがサーバーに送信されます。 </form> 要素を閉じる前に最後の行では、フォーム タグ ヘルパーによって生成された非表示の XSRF トークンが示されています。

POST 要求の処理

次のリストでは、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);
}

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

その他の技術情報