共用方式為


檢驗電影控制器的「編輯動作方法」和「檢視」

作者:Rick Anderson

注意

本教學課程的更新版本可在此取得,它使用最新版的 Visual Studio。 新的教學課程會使用 ASP.NET Core MVC,它在本教學課程提供多種改良。

本教學課程可讓您了解 ASP.NET Core MVC 與控制器和檢視。 Razor 頁面是 ASP.NET Core 中的新替代方案,它是以頁面為基礎的程式設計模型,可讓 Web UI 的建立更容易且更有效率。 建議您在嘗試使用 MVC 版本之前,先試試 Razor 頁面教學課程。 Razor 頁面教學課程:

  • 比較容易學習。
  • 涵蓋更多功能。
  • 是開發新應用程式的建議方法。

在本節中,您將檢驗針對電影控制器所產生的 Edit 動作方法和檢視。 但首先,我們先暫時分流,讓發行日期更正確。 開啟 Models/Movie.cs 檔案,然後新增下方所示的反白行:

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

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

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

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

您也可以將日期設為如下的特定文化專屬格式:

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

接下來的教學課程中將涵蓋 DataAnnotationsDisplay 屬性指定要顯示的欄位名稱 (在本例中為 "Release Date",而不是 "ReleaseDate")。 DataType 屬性會指定資料的型別,在此例中是指日期,因此不會顯示儲存在欄位中的時間資訊。 Chrome 瀏覽器無法正確轉譯日期時,需要 DisplayFormat 屬性來修正錯誤。

執行應用程式並前往 Movies 控制器。 將滑鼠指標停留在 [編輯] 連結上,查看它前往的 URL。

EditLink_sm

[編輯] 連結是由 Views\Movies\Index.cshtml 檢視中的 Html.ActionLink 方法所產生:

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

Html.ActionLink

Html 物件這個協助程式會使用 System.Web.Mvc.WebViewPage 基礎類別來公開。 協助程式的 ActionLink 方法可讓您輕鬆地以動態方式產生 HTML 超連結,連結至控制器的動作方法。 ActionLink 方法的第一個引數是需要轉譯的連結文字 (例如 <a>Edit Me</a>)。 第二個引數是需要叫用的動作方法名稱 (在此例中為 Edit 動作)。 最後一個引數是匿名物件,會產生路由資料 (在此例中,ID 為 4)。

上一個影像中顯示的已產生連結為 http://localhost:1234/Movies/Edit/4。 預設路由 (在 App_Start\RouteConfig.cs 中建立) 會採用 URL 模式 {controller}/{action}/{id}。 因此,ASP.NET 會將 http://localhost:1234/Movies/Edit/4 轉譯成對 Movies 控制器提出 Edit 動作方法要求,其參數 ID 等於 4 檢驗 App_Start\RouteConfig.cs 檔案的下列程式碼。 MapRoute 方法可用來將 HTTP 要求路由至正確的控制器和動作方法,並提供選擇性的 ID 參數。 在有控制器、動作方法和任何路由資料的情況下,HtmlHelpers (例如ActionLink) 也會使用 MapRoute 方法產生 URL。

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", 
            id = UrlParameter.Optional }
    );
}

您也可以使用查詢字串傳遞動作方法參數。 舉例來說,URL http://localhost:1234/Movies/Edit?ID=3 也會將 ID 參數 3 傳遞給 Movies 控制器的 Edit 動作方法。

EditQueryString

開啟 Movies 控制器。 以下顯示兩種 Edit 動作方法。

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

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

請注意,第二個 Edit 動作方法的前面是 HttpPost 屬性。 此屬性指定 Edit 方法的多載只能為 POST 要求叫用。 您可以將 HttpGet 屬性套用至第一個編輯方法,但因為它是預設值,所以並不必要。 (我們會參照將屬性 HttpGet 隱含指派為 HttpGet 方法的動作方法。Bind 屬性是另一種重要的安全機制,可讓駭客無法將資料過度發布到您的模型。 您應該只在想要變更的 bind 屬性 (attribute) 中加入屬性 ( property)。 您可以在我的過度發布安全備註參閱過度發布和 bind 屬性。 本教學課程使用的簡單模型中,我們會繫結模型中的所有資料。 ValidateAntiForgeryToken 屬性是用來防範偽造的要求,且會在編輯檢視檔案中與 @Html.AntiForgeryToken()配對 (Views/Movies/Edit.cshtml),這個部分如下所示。

@model MvcMovie.Models.Movie

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

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

@Html.AntiForgeryToken() 會產生隱藏的防偽造權杖,它必須與 Movies 控制器中的 Edit 方法相符。 您可以在我的教學課程 MVC 的 XSRF/CSRF 防範中深入瞭解跨網站要求偽造 (又稱 XSRF 或 CSRF)。

HttpGet Edit 方法會採用電影 ID 參數,運用 Entity Framework Find 方法查詢電影,並將選取的電影傳回「編輯」檢視。 找不到電影時會傳回 HttpNotFound。 當 Scaffolding 系統建立 Edit 檢視時,它會檢查 Movie 類別,並建立程式碼為類別的每個屬性轉譯 <label><input> 元素。 下列範例會顯示 Visual Studio Scaffolding 系統所產生的 Edit 檢視:

@model MvcMovie.Models.Movie

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

        <div class="form-group">
            @Html.LabelFor(model => model.Title, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Title)
                @Html.ValidationMessageFor(model => model.Title)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.ReleaseDate, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.ReleaseDate)
                @Html.ValidationMessageFor(model => model.ReleaseDate)
            </div>
        </div>
        @*Genre and Price removed for brevity.*@        
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

請注意檢視範本的檔案頂端有 @model MvcMovie.Models.Movie 陳述式,它會指定檢視所需的檢視範本模型是類型 Movie

已建立結構的程式碼會使用數個協助程式方法來簡化 HTML 標記。 Html.LabelFor 協助程式會顯示欄位的名稱 (「Title」、「ReleaseDate」、「Genre」 或 「Price」)。 Html.EditorFor 協助程式會轉譯 HTML <input> 元素。 Html.ValidationMessageFor 協助程式則會顯示與該屬性相關聯的任何驗證訊息。

執行應用程式,並前往 /Movies URL。 按一下 Edit 連結。 在瀏覽器中,檢視頁面的原始檔。 表單元素的 HTML 如下所示。

<form action="/movies/Edit/4" method="post">
   <input name="__RequestVerificationToken" type="hidden" value="UxY6bkQyJCXO3Kn5AXg-6TXxOj6yVBi9tghHaQ5Lq_qwKvcojNXEEfcbn-FGh_0vuw4tS_BRk7QQQHlJp8AP4_X4orVNoQnp2cd8kXhykS01" />  <fieldset class="form-horizontal">
      <legend>Movie</legend>

      <input data-val="true" data-val-number="The field ID must be a number." data-val-required="The ID field is required." id="ID" name="ID" type="hidden" value="4" />

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

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

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

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

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

<input> 元素位於 UTML <form> 元素中,而後者的 action 屬性設為發佈到 /Movies/Edit URL。 如果按一下 [儲存] 按鈕,表單資料將發佈至伺服器。 第二行會顯示 @Html.AntiForgeryToken() 呼叫所產生的隱藏 XSRF 權杖。

處理 POST 要求

下列清單顯示 HttpPost 版本的 Edit 動作方法。

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

ValidateAntiForgeryToken 屬性會驗證檢視中 @Html.AntiForgeryToken() 呼叫所產生的 XSRF 權杖。

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

一旦用戶端驗證判斷欄位的值無效,就會顯示錯誤訊息。 如果 JavaScript 已停用,用戶端驗證就會停用。 不過,伺服器會偵測到發布的值無效,且表單值會顯示錯誤訊息。

在本教學課程稍後,我們會更詳細檢驗驗證。

Edit.cshtml 檢視範本的 Html.ValidationMessageFor 協助程式會負責顯示相應的錯誤訊息。

abcNotValid

所有 HttpGet 方法都遵循相似的模式。 它們會取得電影物件 (在 Index 的案例為物件清單),並將模型傳遞至檢視。 Create 方法會將空白電影物件傳遞至「建立」檢視。 建立、編輯、刪除或以其他方式修改資料的所有方法都會在方法的 HttpPost 多載中執行這個動作。 根據部落格文章 ASP.NET MVC 提示 #46 - 請勿使用刪除連結,以免造成安全性漏洞所述,修改 HTTP GET 方法的資料有安全性的風險。 修改 GET 方法的資料也違反 HTTP 最佳做法和架構性 REST 模式,此模式指定 GET 要求不應變更應用程式的狀態。 也就是說,執行 GET 作業應該是安全的作業,沒有任何副作用,而且不會修改您的保存資料。

非英文地區設定的 jQuery 驗證

如果您使用美式英文的電腦,您可以略過本節並前往下一個教學課程。 您可以在這裡下載本教學課程的全球化版本。 如需參閱精心設計的國際化兩部分教學,請參閱 Nadeem 的 ASP.NET MVC 5 國際化

注意

若要針對將逗號 (「,」) 用於小數點的非英文地區設定及非美式英文日期格式支援 jQuery 驗證,您必須加入 globalize.js 和特定 cultures/globalize.cultures.js 檔案 (從 https://github.com/jquery/globalize) 和 JavaScript 才能使用 Globalize.parseFloat。 您可以從 NuGet 取得 jQuery 的非英文驗證。 (如果您使用英文地區設定,請勿安裝 Globalize。)

  1. [工具] 功能表選取 [NuGet 套件管理員],然後按一下 [管理解決方案的 NuGet 套件]

    螢幕擷取畫面中的 [工具] 功能表開始對非英文地區設定進行 jQuery 驗證。

  2. 在左窗格中,選取 [瀏覽*]。(請參閱下圖。)

  3. 在輸入方塊中,輸入「Globalize」*。

    螢幕擷取畫面中的輸入方塊已輸入 Globalize。

    選擇 jQuery.Validation.Globalize,然後選擇 MvcMovie,再按一下 [安裝]Scripts\jquery.globalize\globalize.js 檔案會加到您的專案。 *Scripts\jquery.globalize\cultures* 資料夾將包含多種文化的 JavaScript 檔案。 請注意,安裝此套件可能需要五分鐘。

    下列程式碼所示為修改的 Views\Movies\Edit.cshtml 檔案:

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

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

若要避免在每個「編輯」檢視都重複此程式碼,您可以將它移至版面配置檔案。 若要最佳化指令碼下載,請參閱我的教學課程統合和縮製

如需詳細資訊,請參閱 ASP.NET MVC 3 國際化ASP.NET MVC 3 國際化 - 第 2 部分 (NerdDinner)

如果您無法以您的地區設定進行驗證,您可以強制讓電腦使用美式英文,或停用瀏覽器的 JavaScript,藉此暫時修正問題。 若要強制您的電腦使用美式英文,您可以將全球化元素新增至專案根目錄的 web.config 檔案。 下列程式代碼所示為將文化設為美國英文的全球化元素。

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

在下個教學課程中,我們會實作搜尋功能。