注意
本教學課程的更新版本可在此取得,它使用最新版的 Visual Studio。 新的教學課程會使用 ASP.NET Core MVC,它在本教學課程提供多種改良。
本教學課程可讓您了解 ASP.NET Core MVC 與控制器和檢視。 Razor 頁面是 ASP.NET Core 中的新替代方案,它是以頁面為基礎的程式設計模型,可讓 Web UI 的建立更容易且更有效率。 建議您在嘗試使用 MVC 版本之前,先試試 Razor 頁面教學課程。 Razor 頁面教學課程:
- 比較容易學習。
- 涵蓋更多功能。
- 是開發新應用程式的建議方法。
在本節,您會將驗證邏輯新增至 Movie 模型,確保只要使用者嘗試透過應用程式建立或編輯電影,驗證規則就會強制執行。
保持 DRY
ASP.NET MVC 的核心設計原則之一是「DRY」(「不要自我重複」)。 ASP.NET MVC 的原則是建議您只要指定一次功能或行為,然後讓它反映到應用程式的所有位置。 這會減少您需要撰寫的程式碼數量,並讓您撰寫的程式碼錯誤較不容易出錯,且更容易維護。
ASP.NET MVC 和 Entity Framework Core Code First 所提供的驗證支援就是執行 DRY 準則的絶佳範例。 您可透過宣告的方式在單一位置指定驗證規則 (在模型類別中),讓規則可在應用程式的任何位置強制執行。
以下說明如何在電影應用程式運用此驗證支援。
將驗證規則新增至電影模型
首先,將驗證邏輯新增至 Movie 類別。
開啟 Movie.cs 檔案。 請注意 System.ComponentModel.DataAnnotations 命名空間不包含 System.Web。 DataAnnotations 提供一組內建的驗證屬性,讓您能以宣告的方式套用至任何類別或屬性。 (它也包含格式化屬性,例如 DataType,它可協助進行格式化,但不提供任何驗證。)
接著請更新 Movie 類別,藉此利用內建的 Required、StringLength、RegularExpression 和 Range 驗證屬性。 將 Movie 類別取代為下列項目:
public class Movie
{
public int ID { get; set; }
[StringLength(60, MinimumLength = 3)]
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; }
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
[Required]
[StringLength(30)]
public string Genre { get; set; }
[Range(1, 100)]
[DataType(DataType.Currency)]
public decimal Price { get; set; }
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
[StringLength(5)]
public string Rating { get; set; }
}
StringLength 屬性會設定字串的長度上限,且它會對資料庫設定此限制,因此資料庫結構描述會變更。 以滑鼠右鍵按一下 [伺服器總管] 的 [Movies] 資料表,然後按一下 [開啟表格定義]:
![螢幕擷取畫面顯示伺服器總管的視窗開啟,且位在 [dbo.MoviesDesign] 索引標籤。](adding-validation/_static/image1.png)
上圖可見所有字串欄位都設為 NVARCHAR (MAX)。 我們會使用移轉來更新結構描述。 建立解決方案,然後開啟「套件管理員主控台」視窗,輸入下列命令:
add-migration DataAnnotations
update-database
此命令完成時,Visual Studio 會開啟類別檔案,該檔案會使用指定的名稱 (DataAnnotations) 來定義新的 DbMigration 衍生類別,且 Up 方法中可看到更新結構條件限制的程式碼:
public override void Up()
{
AlterColumn("dbo.Movies", "Title", c => c.String(maxLength: 60));
AlterColumn("dbo.Movies", "Genre", c => c.String(nullable: false, maxLength: 30));
AlterColumn("dbo.Movies", "Rating", c => c.String(maxLength: 5));
}
Genre 欄位不再可為 Null (也就是說,您必須輸入值)。 Rating 欄位的長度上限為 5,Title 的長度上限為 60。 Title 的長度下限為 3,而 Price 的範圍則未建立結構描述變更。
檢驗 Movie 的結構描述:
![螢幕擷取畫面所示為 [dbo.MoviesDesign] 索引標籤。「標題」和「評分」已勾選 [允許 Null] 欄。](adding-validation/_static/image2.png)
字串欄位顯示新的長度限制,且 Genre 不再勾選「可為 Null」。
驗證屬性會指定您想要強制執行模型屬性套用的行為。 Required 和 MinimumLength 屬性 (attribute) 指出屬性 (property) 必須是值;但無法防止使用者輸入空格以滿足此驗證。 RegularExpression 屬性係用來限制可輸入的字元。 上述程式碼中的 Genre 和 Rating 必須只使用字母 (不允許使用空白字元、數字及特殊字元)。 Range 屬性會將值限制在指定的範圍內。 StringLength 屬性可讓您設定字串屬性的最大長度,並選擇性設定其最小長度。 值型別 (如 decimal, int, float, DateTime) 原本就是必要項目,且不需要 Required 屬性。
Code First 會先確保您對模型類別指定的驗證規則會強制執行,才讓應用程式儲存資料庫的變更。 舉例來說,下列程式碼會在呼叫 SaveChanges 方法時擲回 DbEntityValidationException 例外狀況,因為數個必要的 Movie 屬性值遺漏:
MovieDBContext db = new MovieDBContext();
Movie movie = new Movie();
movie.Title = "Gone with the Wind";
db.Movies.Add(movie);
db.SaveChanges(); // <= Will throw server side validation exception
上述程式碼擲回以下例外狀況:
一個或多個實體驗證失敗。 如需詳細資訊,請參閱 'EntityValidationErrors' 屬性。
擁有 .NET Framework 自動強制執行的驗證規則,有助於讓應用程式更穩固。 它也確保您不會忘記要驗證某些項目,不小心讓不正確的資料進入資料庫。
ASP.NET MVC 的驗證錯誤 UI
執行應用程式,並前往 /Movies URL。
按一下 [新建] 連結以新增新電影。 使用無效值填寫表單。 jQuery 用戶端驗證一偵測到錯誤,就會顯示錯誤訊息。

注意
若要針對將逗號 (「,」) 用於小數點的非英文地區支援 jQuery 驗證,您必須按照本教學課程之前所述,加入全球化的 NuGet。
請注意,表單如何自動使用紅色框線來標示包含無效資料的文字框,並在每個文字框旁邊顯示對應的驗證錯誤訊息。 用戶端 (使用 JavaScript 和 jQuery) 與伺服器端 (若使用者已停用 JavaScript 時) 都會強制執行這些錯誤。
真正的益處在於,您不需要為了啟用這項驗證 UI 而變更 MoviesController 類別或 Create.cshtml 檢視的任一行程式碼。 您稍早在本教學課程中建立的控制器和檢視會自動拾取您指定的驗證規則 (在 Movie 模型類別的屬性 (property) 上使用驗證屬性 (attribute))。 使用 Edit 動作方法測試驗證,即會套用相同的驗證。
直到沒有任何用戶端驗證錯誤後,表單資料才會傳送至伺服器。 藉由使用 Fiddler 工具,或 IE 的 F12 開發人員工具,您可以將中斷點放入 HTTP Post 方法來進行驗證。
「建立檢視」和「建立動作」方法如何執行驗證
您可能奇怪如何在不更新控制器或檢視程式碼的狀況下產生驗證 UI。 下個清單顯示 Create 方法在 MovieController 類別中的外觀。 這些方法與您稍早在本教學課程建立者無異。
public ActionResult Create()
{
return View();
}
// POST: /Movies/Create
// 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 Create([Bind(Include = "ID,Title,ReleaseDate,Genre,Price,Rating")] 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,用戶端驗證也會停用,且測試 HTTP POST Create 方法會取得 ModelState.IsValid 來檢查電影是否有任何驗證錯誤。
您可以在 HttpPost Create 方法中設定中斷點,並確認永遠不會呼叫該方法,用戶端驗證就不會在偵測到驗證錯誤時,提交表單資料。 如果您停用瀏覽器的 JavaScript,然後提交有錯誤的表單,就會叫用中斷點。 您仍可使用沒有 JavaScript 的完整驗證。 下圖說明如何在 Internet Explorer 停用 JavaScript。
![螢幕擷取畫面顯示 [網際網路選項] 視窗開啟,且位在 [安全性] 索引標籤。[自訂層級] 視窗開啟,且 [動態指令碼處理] 已停用。](adding-validation/_static/image5.png)

下圖顯示如何停用 FireFox 瀏覽器的 JavaScript。
![螢幕擷取畫面顯示 [選項] 視窗。內容已選取,且 [啟用 Java 指令碼] 以紅色圈選。](adding-validation/_static/image7.png)
下圖顯示如何停用 Chrome 瀏覽器的 JavaScript。

以下是您稍早在本教學課程中建立結構的 Create.cshtml 檢視範本。 上述動作方法會使用它來顯示初始表單,以及在發生錯誤時重新顯示它。
@model MvcMovie.Models.Movie
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Movie</h4>
<hr />
@Html.ValidationSummary(true)
<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>
@*Fields removed for brevity.*@
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
請注意程式碼如何使用 Html.EditorFor 協助程序來輸出每個 Movie 屬性的 <input> 元素。 此協助程式旁邊是 Html.ValidationMessageFor 協助程式方法的呼叫。 這兩個協助程式方法會使用控制器傳遞給檢視的模型物件 (在此例中為 Movie 物件)。 它們會自動尋找模型指定的驗證屬性,並視情況顯示錯誤訊息。
此方法最好的一點在於,控制器和 Create 檢視範本對要強制執行的實際驗證規則,或顯示的特定錯誤訊息,全都一無所知。 只有在 Movie 類別中才能指定驗證規則和錯誤字串。 這些相同的驗證規則會自動套用到 Edit 檢視,以及任何其他您可能建立以編輯模型的檢視範本。
如果您想要變更驗證邏輯,在模型中新增驗證屬性 (本例中為 movie 類別),即可在單一位置完成作業。 您不必擔心應用程式的不同部分會與規則強制執行的方式不一致,所有的驗證邏輯都是在同一個地方定義,用於所有位置。 這會讓程式碼非常整齊乾淨,容易維護及發展。 也就是說,您完全彰顯了 DRY 原則。
使用 DataType 屬性
開啟 Movie.cs 檔案並檢查 Movie 類別。 除了一組內建的驗證屬性之外,System.ComponentModel.DataAnnotations 命名空間還提供了格式屬性。 發行日期和價格欄位已經套用 DataType 列舉值。 下列程式碼會示範具有適當 DataType 屬性 (attribute) 的 ReleaseDate 和 Price 屬性 (property)。
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
[DataType(DataType.Currency)]
public decimal Price { get; set; }
DataType 屬性只會提供提示讓檢視引擎格式化資料的提示 (並分別為 URL 及電子郵件提供 <a> 和 <a href="mailto:EmailAddress.com"> 等屬性)。 您可使用 RegularExpression 屬性来驗證資料的格式。 DataType 屬性是用來指定比資料庫內建類型更特殊的資料類型,它們不是驗證屬性。 在此案例中,我們只想要追蹤日期,而非日期和時間。 DataType 列舉提供許多資料類型,例如 Date、Time、PhoneNumber、Currency、EmailAddress 等。 DataType 屬性也可讓應用程式自動提供類型的特定功能。 舉例來說,DataType.EmailAddress可建立 mailto: 連結,且在支援 HTML5 的瀏覽器可使用 DataType.Date 的日期選擇器。 DataType 屬性會提供 HTML 5 瀏覽器可接受的 HTML 5 data- (讀為 data dash) 屬性。 DataType 屬性不會提供任何驗證。
DataType.Date 未指定顯示日期的格式。 根據預設,資料欄位會根據以伺服器的 CultureInfo 為基礎的預設格式來顯示。
DisplayFormat 屬性用來明確指定日期格式:
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
ApplyFormatInEditMode 設定會指定在要編輯的文字方塊顯示值時,也套用指定的格式。 (某些欄位可能不適合這種方法,例如貨幣值;您可能不希望在要編輯的文字方塊中出現貨幣符號。)
您可單獨使用 DisplayFormat 屬性,但通常建議一並使用 DataType 屬性。 DataType 屬性會傳逹資料的語意,而不是在螢幕上呈現資料的方式,並提供下列 DisplayFormat 不具備的優點:
- 瀏覽器可以啟用 HTML5 功能 (例如顯示日曆控制項、地區專屬的貨幣符號、電子郵件連結等)。
- 根據預設,瀏覽器會根據您的地區設定,使用正確的格式來呈現資料。
- DataType 屬性可以讓 MVC 選擇正確的欄位範本來轉譯資料 (如果單獨使用,DisplayFormat 會使用字串範本)。 如需詳細資訊,請參閱 Brad Wilson 的 ASP.NET MVC 2 範本。 (雖然本文章針對 MVC 2 而撰寫,仍適用於目前的 ASP.NET MVC 版本。)
如果您使用 DataType 屬性來搭配日期欄位,也必須指定 DisplayFormat 屬性,才能確保欄位在 Chrome 瀏覽器中正確呈現。 如需更多資訊,請參閱這則 StackOverflow 討論串。
注意
jQuery 驗證不適用於 Range 屬性和 DateTime。 例如,下列程式碼一律會顯示用戶端驗證錯誤,即使當日期位在指定範圍中也一樣:
[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]
您需要停用 jQuery 日期驗證,才能使用 Range 屬性搭配 DateTime。 編譯模型中的硬性日期通常不是良好的做法,因此不建議使用 Range 屬性和 DateTime。
下列程式碼會顯示一行上的結合屬性:
public class Movie
{
public int ID { get; set; }
[Required,StringLength(60, MinimumLength = 3)]
public string Title { get; set; }
[Display(Name = "Release Date"),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; }
[Required,StringLength(5)]
public string Rating { get; set; }
}
在數列的下一個部分中,我們會檢閱應用程式,並對自動產生的 Details 和 Delete 方法進行一些改良。