將驗證邏輯新增至電影模型
注意
這裡提供本教學課程的更新版本,其中使用 ASP.NET MVC 5 和 Visual Studio 2013。 新的教學指示更安全、更易於遵循且示範更多功能。
在本節,您會將驗證邏輯新增至 Movie
模型,確保只要使用者嘗試透過應用程式建立或編輯電影,驗證規則就會強制執行。
保持 DRY
ASP.NET MVC 的核心設計原則之一是「DRY」(「不要自我重複」)。 ASP.NET MVC 的原則是建議您只要指定一次功能或行為,然後讓它反映到應用程式的所有位置。 這會減少您需要撰寫的程式碼數量,並讓您撰寫的程式碼錯誤較不容易出錯,且更容易維護。
ASP.NET MVC 和 Entity Framework Core Code First 所提供的驗證支援就是執行 DRY 準則的絶佳範例。 您可透過宣告的方式在單一位置指定驗證規則 (在模型類別中),讓規則可在應用程式的任何位置強制執行。
以下說明如何在電影應用程式運用此驗證支援。
將驗證規則新增至電影模型
首先,將驗證邏輯新增至 Movie
類別。
開啟 Movie.cs 檔案。 在檔案頂端新增 using
陳述式,並參照 System.ComponentModel.DataAnnotations
命名空間:
using System.ComponentModel.DataAnnotations;
請注意命名空間不包含 System.Web
。 DataAnnotations 提供一組內建的驗證屬性,讓您能以宣告的方式套用至任何類別或屬性。
現在,請更新 Movie
類別,以利用內建的 Required
、StringLength
和 Range
驗證屬性。 以下列程式碼作為套用屬性的位置範例。
public class Movie {
public int ID { get; set; }
[Required]
public string Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Required]
public string Genre { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[StringLength(5)]
public string Rating { get; set; }
}
執行應用程式,您將會再次收到下列運行時間錯誤:
建立資料庫之後,備份 『MovieDBContext』 內容的模型已變更。請考慮使用 Code First 移轉 來更新資料庫 (https://go.microsoft.com/fwlink/?LinkId=238269)。
我們會使用移轉來更新結構描述。 建立解決方案,然後開啟「套件管理員主控台」視窗,輸入下列命令:
add-migration AddDataAnnotationsMig
update-database
當此命令完成時,Visual Studio 會開啟類別檔案,這個檔案會使用指定的名稱來定義新的 DbMigration
衍生類別(AddDataAnnotationsMig),並在 方法中 Up
,您可以看到更新架構條件約束的程序代碼。 Title
和 Genre
欄位已不再可為 Null(也就是說,您必須輸入值),且Rating
欄位的長度上限為 5。
驗證屬性會指定您想要強制執行模型屬性套用的行為。 Required
屬性指示,屬性必須有值;在本例中,電影必須有 Title
、ReleaseDate
、Genre
和 Price
屬性,才視為有效。 Range
屬性會將值限制在指定的範圍內。 StringLength
屬性可讓您設定字串屬性的最大長度,並選擇性設定其最小長度。 內部類型 (例如 ) decimal, int, float, DateTime
預設為必要類型,且不需要 Required
屬性。
Code First 會先確保您對模型類別指定的驗證規則將強制執行,應用程式才會將變更儲存至資料庫。 例如,下列程式碼會在呼叫 SaveChanges
方法時擲回例外狀況,因為缺少幾個必要的 Movie
屬性值,且價格是零 (超出有效範圍)。
MovieDBContext db = new MovieDBContext();
Movie movie = new Movie();
movie.Title = "Gone with the Wind";
movie.Price = 0.0M;
db.Movies.Add(movie);
db.SaveChanges(); // <= Will throw server side validation exception
由 .NET Framework 自動強制執行驗證規則可強化應用程式。 它也確保您不會忘記要驗證某些項目,不小心讓不正確的資料進入資料庫。
以下是更新版「Movie.cs」 檔案的完整程式碼清單:
using System;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;
namespace MvcMovie.Models {
public class Movie {
public int ID { get; set; }
[Required]
public string Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Required]
public string Genre { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[StringLength(5)]
public string Rating { get; set; }
}
public class MovieDBContext : DbContext {
public DbSet<Movie> Movies { get; set; }
}
}
ASP.NET MVC 的驗證錯誤 UI
重新執行應用程式,並瀏覽至 /Movies URL。
按一下 [新建] 連結以新增新電影。 以一些無效值填入表單,然後按一下 [建立] 按鈕。
注意
若要針對小數點使用逗號 (“,”) 的非英文地區設定支援 jQuery 驗證,您必須包含globalize.js和特定文化特性/globalize.cultures.js檔案(from https://github.com/jquery/globalize ) 和 JavaScript 才能使用 Globalize.parseFloat
。 下列程式代碼顯示 Views\Movies\Edit.cshtml 檔案的修改,以使用 “fr-FR” 文化特性:
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
<script src="~/Scripts/globalize.js"></script>
<script src="~/Scripts/globalize.culture.fr-FR.js"></script>
<script>
$.validator.methods.number = function (value, element) {
return this.optional(element) ||
!isNaN(Globalize.parseFloat(value));
}
$(document).ready(function () {
Globalize.culture('fr-FR');
});
</script>
<script>
jQuery.extend(jQuery.validator.methods, {
range: function (value, element, param) {
//Use the Globalization plugin to parse the value
var val = $.global.parseFloat(value);
return this.optional(element) || (
val >= param[0] && val <= param[1]);
}
});
</script>
}
請注意,表單如何自動使用紅色框線來標示包含無效資料的文字框,並在每個文字框旁邊顯示對應的驗證錯誤訊息。 用戶端 (使用 JavaScript 和 jQuery) 與伺服器端 (若使用者已停用 JavaScript 時) 都會強制執行這些錯誤。
真正的益處在於,您不需要為了啟用這項驗證 UI 而變更 MoviesController
類別或 Create.cshtml 檢視的任一行程式碼。 您稍早在本教學課程中建立的控制器和檢視會自動拾取您指定的驗證規則 (在 Movie
模型類別的屬性 (property) 上使用驗證屬性 (attribute))。
您可能已經注意到屬性 Title
和 Genre
,在提交窗體之前,不會強制執行必要的屬性(點擊 [建立 ] 按鈕),或輸入文字到輸入欄位中並移除它。 對於一開始是空的欄位(例如[建立檢視] 上的欄位,以及只有必要屬性且沒有其他驗證屬性的欄位,您可以執行下列動作來觸發驗證:
- 索引標籤到欄位。
- 輸入一些文字。
- 按下 Tab 鍵切換至下一個欄位。
- 將索引標籤回到欄位。
- 拿掉文字。
- 按下 Tab 鍵切換至下一個欄位。
上述順序會觸發必要的驗證,而不按 [提交] 按鈕。 只要按 [提交] 按鈕而不輸入任何字段,就會觸發客戶端驗證。 直到沒有任何用戶端驗證錯誤後,表單資料才會傳送至伺服器。 您可以將斷點放在 HTTP Post 方法中,或使用 fiddler 工具 或 IE 9 F12 開發人員工具進行測試。
「建立檢視」和「建立動作」方法如何執行驗證
您可能奇怪如何在不更新控制器或檢視程式碼的狀況下產生驗證 UI。 下個清單顯示 Create
方法在 MovieController
類別中的外觀。 這些方法與您稍早在本教學課程建立者無異。
//
// GET: /Movies/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Movies/Create
[HttpPost]
public ActionResult Create(Movie movie)
{
if (ModelState.IsValid)
{
db.Movies.Add(movie);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
第一個 (HTTP GET)Create
動作方法會顯示初始建立表單。 第二個 ([HttpPost]
) 版本處理表單張貼。 第二個 Create
方法 (HttpPost
版本) 會呼叫ModelState.IsValid
檢查電影是否有任何驗證錯誤。 呼叫此方法會評估已套用至物件的所有驗證屬性。 如果物件有驗證錯誤,則 Create
方法會重新顯示表單。 如果沒有任何錯誤,方法即會將新的電影儲存到資料庫。 在我們的電影範例中,當用戶端偵測到驗證錯誤時,表單不會張貼到伺服器;永遠不會呼叫第二Create
個方法。 如果您在瀏覽器中停用 JavaScript,則會停用客戶端驗證,並呼叫 ModelState.IsValid
HTTP POST Create
方法來檢查影片是否有任何驗證錯誤。
您可以在 HttpPost Create
方法中設定中斷點,並確認永遠不會呼叫該方法,用戶端驗證就不會在偵測到驗證錯誤時,提交表單資料。 如果您停用瀏覽器的 JavaScript,然後提交有錯誤的表單,就會叫用中斷點。 您仍可使用沒有 JavaScript 的完整驗證。 下圖說明如何在 Internet Explorer 停用 JavaScript。
下圖顯示如何停用 FireFox 瀏覽器的 JavaScript。
下圖顯示如何使用 Chrome 瀏覽器停用 JavaScript。
以下是您稍早在本教學課程中建立結構的 Create.cshtml 檢視範本。 上述動作方法會使用它來顯示初始表單,以及在發生錯誤時重新顯示它。
@model MvcMovie.Models.Movie
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")"></script>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Movie</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Title)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Title)
@Html.ValidationMessageFor(model => model.Title)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.ReleaseDate)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.ReleaseDate)
@Html.ValidationMessageFor(model => model.ReleaseDate)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Genre)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Genre)
@Html.ValidationMessageFor(model => model.Genre)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Price)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Price)
@Html.ValidationMessageFor(model => model.Price)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Rating)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Rating)
@Html.ValidationMessageFor(model => model.Rating)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
請注意程式碼如何使用 Html.EditorFor
協助程序來輸出每個 Movie
屬性的 <input>
元素。 此協助程式旁邊是 Html.ValidationMessageFor
協助程式方法的呼叫。 這兩個協助程式方法會使用控制器傳遞給檢視的模型物件 (在此例中為 Movie
物件)。 這些方法會自動尋找模型指定的驗證屬性,並視情況顯示錯誤訊息。
這個方法最棒的是,控制器和 Create 檢視範本對要強制執行的實際驗證規則,或顯示的特定錯誤訊息,全都一無所知。 只有在 Movie
類別中才能指定驗證規則和錯誤字串。 這些相同的驗證規則會自動套用至 [編輯] 檢視,以及您可能建立以編輯模型的任何其他檢視範本。
如果您想要變更驗證邏輯,在模型中新增驗證屬性 (本例中為 movie
類別),即可在單一位置完成作業。 您不必擔心應用程式的不同部分會與規則強制執行的方式不一致,所有的驗證邏輯都是在同一個地方定義,用於所有位置。 這會讓程式碼非常整齊乾淨,容易維護及發展。 也就是說,您完全彰顯了 DRY 原則。
新增格式設定至電影模型
開啟 Movie.cs 檔案並檢查 Movie
類別。 除了一組內建的驗證屬性之外,System.ComponentModel.DataAnnotations
命名空間還提供了格式屬性。 發行日期和價格欄位已經套用 DataType
列舉值。 下列程式碼會示範具有適當 DisplayFormat
屬性 (attribute) 的 ReleaseDate
和 Price
屬性 (property)。
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[DataType(DataType.Currency)]
public decimal Price { get; set; }
這些 DataType
屬性不是驗證屬性,用來告訴檢視引擎如何轉譯 HTML。 在上述範例中 DataType.Date
,屬性只會將電影日期顯示為日期,而沒有時間。 例如,下列 DataType
屬性不會驗證資料的格式:
[DataType(DataType.EmailAddress)]
[DataType(DataType.PhoneNumber)]
[DataType(DataType.Url)]
上述屬性只會提供提示給檢視引擎格式化數據(並提供屬性,例如 <> URL的,以及<電子郵件的 href=“mailto:EmailAddress.com”。> 您可使用 RegularExpression 屬性来驗證資料的格式。
使用 DataType
屬性的替代方法,您可以明確設定 DataFormatString
值。 下列程式碼顯示具有日期格式字串 (即「d」) 的版本日期屬性。 您可使用此方式指定不要在版本日期中顯示時間。
[DisplayFormat(DataFormatString = "{0:d}")]
public DateTime ReleaseDate { get; set; }
完整的 Movie
類別顯示如下。
public class Movie {
public int ID { get; set; }
[Required]
public string Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[Required]
public string Genre { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[StringLength(5)]
public string Rating { get; set; }
}
執行應用程式並瀏覽至 Movies
控制器。 發行日期和價格的格式正確。 下圖顯示使用 「fr-FR」 作為文化特性的發行日期和價格。
下圖顯示與預設文化特性 (英文 US) 顯示的相同數據。
在數列的下一個部分中,我們會檢閱應用程式,並對自動產生的 Details
和 Delete
方法進行一些改良。