共用方式為


第 6 部分,ASP.NET Core 中的控制器方法和檢視

注意

這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。

警告

不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。

重要

這些發行前產品的相關資訊在產品正式發行前可能會有大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。

如需目前的版本,請參閱 本文的 .NET 9 版本。

作者:Rick Anderson

此電影應用程式有個不錯的開始,但呈現效果不盡理想,例如 ReleaseDate 應該是兩個字。

索引檢視:Release Date (發行日期) 是一個字 (不含空格),且每個電影的發行日期均顯示 12 AM 的時間

如下所示,開啟 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; }
}

在下一個教學課程中會說明 DataAnnotationsDisplay 屬性指定要顯示的欄位名稱 (在本例中為 "Release Date",而不是 "ReleaseDate")。 DataType 屬性指定資料的類型 (Date),因此不會顯示儲存在欄位中的時間資訊。

[Column(TypeName = "decimal(18, 2)")] 資料註解為必要項,因此 Entity Framework Core 可將 Price 正確對應到資料庫中的貨幣。 如需詳細資訊,請參閱資料類型

瀏覽至 Movies 控制器,並將滑鼠指標停留在 Edit 連結,以查看目標 URL。

滑鼠停留在 Edit 連結並顯示 https://localhost:5001/Movies/Edit/5 的 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 會從控制器動作方法和路由識別碼動態產生 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 控制器,並檢查兩個 Edit 動作方法。 下列程式碼示範 HTTP GET Edit 方法,這個方法會擷取電影,並填入 Edit.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] 屬性是一種防止過度發佈的方式。 您應該只在想要變更的 [Bind] 屬性 (attribute) 中包含屬性 ( property)。 如需詳細資訊,請參閱保護控制器避免過度發佈ViewModels 提供防止過度發佈的替代方法。

請注意,第二個 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 方法。 您可以將 [HttpGet] 屬性套用至第一個編輯方法,但不需要執行此動作,因為 [HttpGet] 是預設值。

ValidateAntiForgeryToken 屬性是用來 防範偽造要求 ,並與編輯檢視表檔案 (Views/Movies/Edit.cshtml) 所產生的防偽權杖成對。 編輯檢視表檔案使用 表單標記協助程式產生防偽權杖。

<form asp-action="Edit">

表單標記協助程式會產生隱藏的防偽權杖,其必須符合電影控制器的 Edit 方法中 [ValidateAntiForgeryToken] 產生的防偽權杖。 如需詳細資訊,請參閱防止 ASP.NET Core 中的跨網站要求偽造 (XSRF/CSRF) 攻擊

HttpGet Edit 方法會採用電影 ID 參數,使用 Entity Framework FindAsync 方法查詢電影,並將選取的電影傳回 Edit 檢視。 如果找不到電影,會傳回 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);
}

當 Scaffolding 系統建立 Edit 檢視時,它會檢查 Movie 類別,並建立程式碼為類別的每個屬性轉譯 <label><input> 元素。 下列範例會顯示 Visual Studio Scaffolding 系統所產生的 Edit 檢視:

@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 型別。

包含 Scaffold 的程式碼會使用數個標記協助程式方法來簡化 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> 元素位於 HTML <form> 元素中,而後者的 action 屬性設定為發佈到 /Movies/Edit/id URL。 按一下 Save 按鈕時,表單資料將發佈至伺服器。 在結尾 </form> 元素之前的最後一行會顯示表單標記協助程式所產生的隱藏 XSRF 語彙基元。

處理 POST 要求

下列清單顯示 [HttpPost] 版本的 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);
}

[ValidateAntiForgeryToken] 屬性驗證 表單標記協助程式中防偽權杖產生器所產生的隱藏 XSRF 權杖

模型繫結系統採用已發佈的表單值,並建立以 movie 參數傳遞的 Movie 物件。 ModelState.IsValid 屬性會驗證表單中提交的資料可用於修改 (編輯或更新) Movie 物件。 如果資料有效,則會進行儲存。 藉由呼叫資料庫內容的 SaveChangesAsync 方法,更新 (編輯) 的電影資料會儲存到資料庫。 儲存資料之後,程式碼將使用者重新導向至 MoviesController 類別的 Index 動作方法,此方法會顯示電影集合,包括剛剛所進行的變更。

在表單發佈至伺服器之前,用戶端驗證會對欄位檢查任何驗證規則。 如果出現任何驗證錯誤,即會顯示錯誤訊息,且不會發佈該表單。 如果已停用 JavaScript,就不會進行用戶端驗證,但伺服器偵測到無效的發佈值,因此會重新顯示表單值並顯示錯誤訊息。 稍後在本教學課程中,我們會更詳細檢查模型驗證Views/Movies/Edit.cshtml 檢視範本中的驗證標記協助程式負責顯示適當的錯誤訊息。

Edit 檢視:Price 值 abc 不正確的例外狀況指出 Price 欄位必須是數字。Release Date 值 xyz 不正確的例外狀況指出請輸入有效的日期。

電影控制器中的所有 HttpGet 方法都遵循類似的模式。 他們會取得電影物件 (如果是 Index則為物件清單),並將此物件 (模型) 傳遞至檢視。 Create 方法會將空白電影物件傳遞至 Create 檢視。 建立、編輯、刪除或以其他方式修改資料的所有方法都會在方法的 [HttpPost] 多載中執行這個動作。 修改 HTTP GET 方法中的資料會造成安全性風險。 修改 HTTP GET 方法中的資料,也違反 HTTP 最佳做法及架構式 REST 模式,此模式指定 GET 要求不應該變更應用程式的狀態。 也就是說,執行 GET 作業應該是安全的作業,沒有任何副作用,而且不會修改您的保存資料。

其他資源

此電影應用程式有個不錯的開始,但呈現效果不盡理想,例如 ReleaseDate 應該是兩個字。

索引檢視:Release Date (發行日期) 是一個字 (不含空格),且每個電影的發行日期均顯示 12 AM 的時間

如下所示,開啟 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; }
}

在下一個教學課程中會說明 DataAnnotationsDisplay 屬性指定要顯示的欄位名稱 (在本例中為 "Release Date",而不是 "ReleaseDate")。 DataType 屬性指定資料的類型 (Date),因此不會顯示儲存在欄位中的時間資訊。

[Column(TypeName = "decimal(18, 2)")] 資料註解為必要項,因此 Entity Framework Core 可將 Price 正確對應到資料庫中的貨幣。 如需詳細資訊,請參閱資料類型

瀏覽至 Movies 控制器,並將滑鼠指標停留在 Edit 連結,以查看目標 URL。

滑鼠停留在 Edit 連結並顯示 https://localhost:5001/Movies/Edit/5 的 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 會從控制器動作方法和路由識別碼動態產生 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 控制器,並檢查兩個 Edit 動作方法。 下列程式碼示範 HTTP GET Edit 方法,這個方法會擷取電影,並填入 Edit.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] 屬性是一種防止過度發佈的方式。 您應該只在想要變更的 [Bind] 屬性 (attribute) 中包含屬性 ( property)。 如需詳細資訊,請參閱保護控制器避免過度發佈ViewModels 提供防止過度發佈的替代方法。

請注意,第二個 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 方法。 您可以將 [HttpGet] 屬性套用至第一個編輯方法,但不需要執行此動作,因為 [HttpGet] 是預設值。

ValidateAntiForgeryToken 屬性是用來 防範偽造要求 ,並與編輯檢視表檔案 (Views/Movies/Edit.cshtml) 所產生的防偽權杖成對。 編輯檢視表檔案使用 表單標記協助程式產生防偽權杖。

<form asp-action="Edit">

表單標記協助程式會產生隱藏的防偽權杖,其必須符合電影控制器的 Edit 方法中 [ValidateAntiForgeryToken] 產生的防偽權杖。 如需詳細資訊,請參閱防止 ASP.NET Core 中的跨網站要求偽造 (XSRF/CSRF) 攻擊

HttpGet Edit 方法會採用電影 ID 參數,使用 Entity Framework FindAsync 方法查詢電影,並將選取的電影傳回 Edit 檢視。 如果找不到電影,會傳回 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);
}

當 Scaffolding 系統建立 Edit 檢視時,它會檢查 Movie 類別,並建立程式碼為類別的每個屬性轉譯 <label><input> 元素。 下列範例會顯示 Visual Studio Scaffolding 系統所產生的 Edit 檢視:

@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 型別。

包含 Scaffold 的程式碼會使用數個標記協助程式方法來簡化 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> 元素位於 HTML <form> 元素中,而後者的 action 屬性設定為發佈到 /Movies/Edit/id URL。 按一下 Save 按鈕時,表單資料將發佈至伺服器。 在結尾 </form> 元素之前的最後一行會顯示表單標記協助程式所產生的隱藏 XSRF 語彙基元。

處理 POST 要求

下列清單顯示 [HttpPost] 版本的 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);
}

[ValidateAntiForgeryToken] 屬性驗證 表單標記協助程式中防偽權杖產生器所產生的隱藏 XSRF 權杖

模型繫結系統採用已發佈的表單值,並建立以 movie 參數傳遞的 Movie 物件。 ModelState.IsValid 屬性會驗證表單中提交的資料可用於修改 (編輯或更新) Movie 物件。 如果資料有效,則會進行儲存。 藉由呼叫資料庫內容的 SaveChangesAsync 方法,更新 (編輯) 的電影資料會儲存到資料庫。 儲存資料之後,程式碼將使用者重新導向至 MoviesController 類別的 Index 動作方法,此方法會顯示電影集合,包括剛剛所進行的變更。

在表單發佈至伺服器之前,用戶端驗證會對欄位檢查任何驗證規則。 如果出現任何驗證錯誤,即會顯示錯誤訊息,且不會發佈該表單。 如果已停用 JavaScript,就不會進行用戶端驗證,但伺服器偵測到無效的發佈值,因此會重新顯示表單值並顯示錯誤訊息。 稍後在本教學課程中,我們會更詳細檢查模型驗證Views/Movies/Edit.cshtml 檢視範本中的驗證標記協助程式負責顯示適當的錯誤訊息。

Edit 檢視:Price 值 abc 不正確的例外狀況指出 Price 欄位必須是數字。Release Date 值 xyz 不正確的例外狀況指出請輸入有效的日期。

電影控制器中的所有 HttpGet 方法都遵循類似的模式。 他們會取得電影物件 (如果是 Index則為物件清單),並將此物件 (模型) 傳遞至檢視。 Create 方法會將空白電影物件傳遞至 Create 檢視。 建立、編輯、刪除或以其他方式修改資料的所有方法都會在方法的 [HttpPost] 多載中執行這個動作。 修改 HTTP GET 方法中的資料會造成安全性風險。 修改 HTTP GET 方法中的資料,也違反 HTTP 最佳做法及架構式 REST 模式,此模式指定 GET 要求不應該變更應用程式的狀態。 也就是說,執行 GET 作業應該是安全的作業,沒有任何副作用,而且不會修改您的保存資料。

其他資源

此電影應用程式有個不錯的開始,但呈現效果不盡理想,例如 ReleaseDate 應該是兩個字。

索引檢視:Release Date (發行日期) 是一個字 (不含空格),且每個電影的發行日期均顯示 12 AM 的時間

如下所示,開啟 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; }
}

在下一個教學課程中會說明 DataAnnotationsDisplay 屬性指定要顯示的欄位名稱 (在本例中為 "Release Date",而不是 "ReleaseDate")。 DataType 屬性指定資料的類型 (Date),因此不會顯示儲存在欄位中的時間資訊。

[Column(TypeName = "decimal(18, 2)")] 資料註解為必要項,因此 Entity Framework Core 可將 Price 正確對應到資料庫中的貨幣。 如需詳細資訊,請參閱資料類型

瀏覽至 Movies 控制器,並將滑鼠指標停留在 Edit 連結,以查看目標 URL。

滑鼠停留在 Edit 連結並顯示 https://localhost:5001/Movies/Edit/5 的 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 會從控制器動作方法和路由識別碼動態產生 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 控制器,並檢查兩個 Edit 動作方法。 下列程式碼示範 HTTP GET Edit 方法,這個方法會擷取電影,並填入 Edit.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] 屬性是一種防止過度發佈的方式。 您應該只在想要變更的 [Bind] 屬性 (attribute) 中包含屬性 ( property)。 如需詳細資訊,請參閱保護控制器避免過度發佈ViewModels 提供防止過度發佈的替代方法。

請注意,第二個 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 方法。 您可以將 [HttpGet] 屬性套用至第一個編輯方法,但不需要執行此動作,因為 [HttpGet] 是預設值。

ValidateAntiForgeryToken 屬性是用來 防範偽造要求 ,並與編輯檢視表檔案 (Views/Movies/Edit.cshtml) 所產生的防偽權杖成對。 編輯檢視表檔案使用 表單標記協助程式產生防偽權杖。

<form asp-action="Edit">

表單標記協助程式會產生隱藏的防偽權杖,其必須符合電影控制器的 Edit 方法中 [ValidateAntiForgeryToken] 產生的防偽權杖。 如需詳細資訊,請參閱防止 ASP.NET Core 中的跨網站要求偽造 (XSRF/CSRF) 攻擊

HttpGet Edit 方法會採用電影 ID 參數,使用 Entity Framework FindAsync 方法查詢電影,並將選取的電影傳回 Edit 檢視。 如果找不到電影,會傳回 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);
}

當 Scaffolding 系統建立 Edit 檢視時,它會檢查 Movie 類別,並建立程式碼為類別的每個屬性轉譯 <label><input> 元素。 下列範例會顯示 Visual Studio Scaffolding 系統所產生的 Edit 檢視:

@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 型別。

包含 Scaffold 的程式碼會使用數個標記協助程式方法來簡化 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> 元素位於 HTML <form> 元素中,而後者的 action 屬性設定為發佈到 /Movies/Edit/id URL。 按一下 Save 按鈕時,表單資料將發佈至伺服器。 在結尾 </form> 元素之前的最後一行會顯示表單標記協助程式所產生的隱藏 XSRF 語彙基元。

處理 POST 要求

下列清單顯示 [HttpPost] 版本的 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);
}

[ValidateAntiForgeryToken] 屬性驗證 表單標記協助程式中防偽權杖產生器所產生的隱藏 XSRF 權杖

模型繫結系統採用已發佈的表單值,並建立以 movie 參數傳遞的 Movie 物件。 ModelState.IsValid 屬性會驗證表單中提交的資料可用於修改 (編輯或更新) Movie 物件。 如果資料有效,則會進行儲存。 藉由呼叫資料庫內容的 SaveChangesAsync 方法,更新 (編輯) 的電影資料會儲存到資料庫。 儲存資料之後,程式碼將使用者重新導向至 MoviesController 類別的 Index 動作方法,此方法會顯示電影集合,包括剛剛所進行的變更。

在表單發佈至伺服器之前,用戶端驗證會對欄位檢查任何驗證規則。 如果出現任何驗證錯誤,即會顯示錯誤訊息,且不會發佈該表單。 如果已停用 JavaScript,就不會進行用戶端驗證,但伺服器偵測到無效的發佈值,因此會重新顯示表單值並顯示錯誤訊息。 稍後在本教學課程中,我們會更詳細檢查模型驗證Views/Movies/Edit.cshtml 檢視範本中的驗證標記協助程式負責顯示適當的錯誤訊息。

Edit 檢視:Price 值 abc 不正確的例外狀況指出 Price 欄位必須是數字。Release Date 值 xyz 不正確的例外狀況指出請輸入有效的日期。

電影控制器中的所有 HttpGet 方法都遵循類似的模式。 他們會取得電影物件 (如果是 Index則為物件清單),並將此物件 (模型) 傳遞至檢視。 Create 方法會將空白電影物件傳遞至 Create 檢視。 建立、編輯、刪除或以其他方式修改資料的所有方法都會在方法的 [HttpPost] 多載中執行這個動作。 修改 HTTP GET 方法中的資料會造成安全性風險。 修改 HTTP GET 方法中的資料,也違反 HTTP 最佳做法及架構式 REST 模式,此模式指定 GET 要求不應該變更應用程式的狀態。 也就是說,執行 GET 作業應該是安全的作業,沒有任何副作用,而且不會修改您的保存資料。

其他資源

此電影應用程式有個不錯的開始,但呈現效果不盡理想,例如 ReleaseDate 應該是兩個字。

索引檢視:Release Date (發行日期) 是一個字 (不含空格),且每個電影的發行日期均顯示 12 AM 的時間

如下所示,開啟 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; }
    }
}

在下一個教學課程中會說明 DataAnnotationsDisplay 屬性指定要顯示的欄位名稱 (在本例中為 "Release Date",而不是 "ReleaseDate")。 DataType 屬性指定資料的類型 (Date),因此不會顯示儲存在欄位中的時間資訊。

[Column(TypeName = "decimal(18, 2)")] 資料註解為必要項,因此 Entity Framework Core 可將 Price 正確對應到資料庫中的貨幣。 如需詳細資訊,請參閱資料類型

瀏覽至 Movies 控制器,並將滑鼠指標停留在 Edit 連結,以查看目標 URL。

滑鼠停留在 Edit 連結並顯示 https://localhost:5001/Movies/Edit/5 的 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 會從控制器動作方法和路由識別碼動態產生 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 控制器,並檢查兩個 Edit 動作方法。 下列程式碼示範 HTTP GET Edit 方法,這個方法會擷取電影,並填入 Edit.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] 屬性是一種防止過度發佈的方式。 您應該只在想要變更的 [Bind] 屬性 (attribute) 中包含屬性 ( property)。 如需詳細資訊,請參閱保護控制器避免過度發佈ViewModels 提供防止過度發佈的替代方法。

請注意,第二個 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 方法。 您可以將 [HttpGet] 屬性套用至第一個編輯方法,但不需要執行此動作,因為 [HttpGet] 是預設值。

ValidateAntiForgeryToken 屬性是用來 防範偽造要求 ,並與編輯檢視表檔案 (Views/Movies/Edit.cshtml) 所產生的防偽權杖成對。 編輯檢視表檔案使用 表單標記協助程式產生防偽權杖。

<form asp-action="Edit">

表單標記協助程式會產生隱藏的防偽權杖,其必須符合電影控制器的 Edit 方法中 [ValidateAntiForgeryToken] 產生的防偽權杖。 如需詳細資訊,請參閱防止 ASP.NET Core 中的跨網站要求偽造 (XSRF/CSRF) 攻擊

HttpGet Edit 方法會採用電影 ID 參數,使用 Entity Framework FindAsync 方法查詢電影,並將選取的電影傳回 Edit 檢視。 如果找不到電影,會傳回 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);
}

當 Scaffolding 系統建立 Edit 檢視時,它會檢查 Movie 類別,並建立程式碼為類別的每個屬性轉譯 <label><input> 元素。 下列範例會顯示 Visual Studio Scaffolding 系統所產生的 Edit 檢視:

@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 型別。

包含 Scaffold 的程式碼會使用數個標記協助程式方法來簡化 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> 元素位於 HTML <form> 元素中,而後者的 action 屬性設定為發佈到 /Movies/Edit/id URL。 按一下 Save 按鈕時,表單資料將發佈至伺服器。 在結尾 </form> 元素之前的最後一行會顯示表單標記協助程式所產生的隱藏 XSRF 語彙基元。

處理 POST 要求

下列清單顯示 [HttpPost] 版本的 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);
}

[ValidateAntiForgeryToken] 屬性驗證 表單標記協助程式中防偽權杖產生器所產生的隱藏 XSRF 權杖

模型繫結系統採用已發佈的表單值,並建立以 movie 參數傳遞的 Movie 物件。 ModelState.IsValid 屬性會驗證表單中提交的資料可用於修改 (編輯或更新) Movie 物件。 如果資料有效,則會進行儲存。 藉由呼叫資料庫內容的 SaveChangesAsync 方法,更新 (編輯) 的電影資料會儲存到資料庫。 儲存資料之後,程式碼將使用者重新導向至 MoviesController 類別的 Index 動作方法,此方法會顯示電影集合,包括剛剛所進行的變更。

在表單發佈至伺服器之前,用戶端驗證會對欄位檢查任何驗證規則。 如果出現任何驗證錯誤,即會顯示錯誤訊息,且不會發佈該表單。 如果已停用 JavaScript,就不會進行用戶端驗證,但伺服器偵測到無效的發佈值,因此會重新顯示表單值並顯示錯誤訊息。 稍後在本教學課程中,我們會更詳細檢查模型驗證Views/Movies/Edit.cshtml 檢視範本中的驗證標記協助程式負責顯示適當的錯誤訊息。

Edit 檢視:Price 值 abc 不正確的例外狀況指出 Price 欄位必須是數字。Release Date 值 xyz 不正確的例外狀況指出請輸入有效的日期。

電影控制器中的所有 HttpGet 方法都遵循類似的模式。 他們會取得電影物件 (如果是 Index則為物件清單),並將此物件 (模型) 傳遞至檢視。 Create 方法會將空白電影物件傳遞至 Create 檢視。 建立、編輯、刪除或以其他方式修改資料的所有方法都會在方法的 [HttpPost] 多載中執行這個動作。 修改 HTTP GET 方法中的資料會造成安全性風險。 修改 HTTP GET 方法中的資料,也違反 HTTP 最佳做法及架構式 REST 模式,此模式指定 GET 要求不應該變更應用程式的狀態。 也就是說,執行 GET 作業應該是安全的作業,沒有任何副作用,而且不會修改您的保存資料。

其他資源

此電影應用程式有個不錯的開始,但呈現效果不盡理想,例如 ReleaseDate 應該是兩個字。

索引檢視:Release Date (發行日期) 是一個字 (不含空格),且每個電影的發行日期均顯示 12 AM 的時間

如下所示,開啟 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; }
    }
}

接下來的教學課程會涵蓋 DataAnnotationsDisplay 屬性指定要顯示的欄位名稱 (在本例中為 "Release Date",而不是 "ReleaseDate")。 DataType 屬性指定資料的類型 (Date),因此不會顯示儲存在欄位中的時間資訊。

[Column(TypeName = "decimal(18, 2)")] 資料註解為必要項,因此 Entity Framework Core 可將 Price 正確對應到資料庫中的貨幣。 如需詳細資訊,請參閱資料類型

瀏覽至 Movies 控制器,並將滑鼠指標停留在 Edit 連結,以查看目標 URL。

滑鼠停留在 Edit 連結並顯示 https://localhost:5001/Movies/Edit/5 的 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 會從控制器動作方法和路由識別碼動態產生 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 控制器,並檢查兩個 Edit 動作方法。 下列程式碼示範 HTTP GET Edit 方法,這個方法會擷取電影,並填入 Edit.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] 屬性是一種防止過度發佈的方式。 您應該只在想要變更的 [Bind] 屬性 (attribute) 中包含屬性 ( property)。 如需詳細資訊,請參閱保護控制器避免過度發佈ViewModels 提供防止過度發佈的替代方法。

請注意,第二個 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 方法。 您可以將 [HttpGet] 屬性套用至第一個編輯方法,但不需要執行此動作,因為 [HttpGet] 是預設值。

ValidateAntiForgeryToken 屬性是用來 防範偽造要求 ,並與編輯檢視表檔案 (Views/Movies/Edit.cshtml) 所產生的防偽權杖成對。 編輯檢視表檔案使用 表單標記協助程式產生防偽權杖。

<form asp-action="Edit">

表單標記協助程式會產生隱藏的防偽權杖,其必須符合電影控制器的 Edit 方法中 [ValidateAntiForgeryToken] 產生的防偽權杖。 如需詳細資訊,請參閱防止 ASP.NET Core 中的跨網站要求偽造 (XSRF/CSRF) 攻擊

HttpGet Edit 方法會採用電影 ID 參數,使用 Entity Framework FindAsync 方法查詢電影,並將選取的電影傳回 Edit 檢視。 如果找不到電影,會傳回 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);
}

當 Scaffolding 系統建立 Edit 檢視時,它會檢查 Movie 類別,並建立程式碼為類別的每個屬性轉譯 <label><input> 元素。 下列範例會顯示 Visual Studio Scaffolding 系統所產生的 Edit 檢視:

@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 型別。

包含 Scaffold 的程式碼會使用數個標記協助程式方法來簡化 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> 元素位於 HTML <form> 元素中,而後者的 action 屬性設定為發佈到 /Movies/Edit/id URL。 按一下 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,就不會進行用戶端驗證,但伺服器偵測到無效的發佈值,因此會重新顯示表單值並顯示錯誤訊息。 稍後在本教學課程中,我們會更詳細檢查模型驗證Views/Movies/Edit.cshtml 檢視範本中的驗證標記協助程式負責顯示適當的錯誤訊息。

Edit 檢視:Price 值 abc 不正確的例外狀況指出 Price 欄位必須是數字。Release Date 值 xyz 不正確的例外狀況指出請輸入有效的日期。

電影控制器中的所有 HttpGet 方法都遵循類似的模式。 他們會取得電影物件 (如果是 Index則為物件清單),並將此物件 (模型) 傳遞至檢視。 Create 方法會將空白電影物件傳遞至 Create 檢視。 建立、編輯、刪除或以其他方式修改資料的所有方法都會在方法的 [HttpPost] 多載中執行這個動作。 修改 HTTP GET 方法中的資料會造成安全性風險。 修改 HTTP GET 方法中的資料,也違反 HTTP 最佳做法及架構式 REST 模式,此模式指定 GET 要求不應該變更應用程式的狀態。 也就是說,執行 GET 作業應該是安全的作業,沒有任何副作用,而且不會修改您的保存資料。

其他資源