本節內容:
- 向
Movie
模型添加了驗證邏輯。 - 確保每當用戶創建或編輯電影時,都會強制執行驗證規則。
堅持 DRY 原則
MVC 的設計原則之一是 DRY(“不要自我重復”)。 ASP.NET Core MVC 支持你僅指定一次功能或行為,然后使它應用到整個應用中。 這可以減少所需編寫的代碼量,並使編寫的代碼更少出錯,更易於測試和維護。
MVC 和 Entity Framework Core Code First 提供的驗證支持是 DRY 原則在實際操作中的極佳示例。 可以在一個位置(模型類中)以聲明方式指定驗證規則,並且在應用中的所有位置強制執行。
將驗證規則添加到電影模型
DataAnnotations 命名空間提供一組內置驗證特性,可通過聲明方式應用於類或屬性。 DataAnnotations 還包含 DataType
等格式特性,有助於格式設置但不提供任何驗證。
更新 Movie
類以使用內置的 Required
、StringLength
、RegularExpression
和 Range
驗證特性。
public class Movie { public int Id { get; set; } [StringLength(60, MinimumLength = 3)] [Required] public string Title { get; set; } [Display(Name = "Release Date")] [DataType(DataType.Date)] public DateTime ReleaseDate { get; set; } [Range(1, 100)] [DataType(DataType.Currency)] [Column(TypeName = "decimal(18, 2)")] public decimal Price { get; set; } [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")] [Required] [StringLength(30)] public string Genre { get; set; } [RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$")] [StringLength(5)] [Required] public string Rating { get; set; } }
驗證特性指定要對應用這些特性的模型屬性強制執行的行為:
-
Required
和MinimumLength
特性表示屬性必須有值;但用戶可輸入空格來滿足此驗證。 -
RegularExpression
特性用於限制可輸入的字符。 在上述代碼中,即“Genre”(分類):- 只能使用字母。
- 第一個字母必須為大寫。 不允許使用空格、數字和特殊字符。
-
RegularExpression
“Rating”(分級):- 要求第一個字符為大寫字母。
- 允許在后續空格中使用特殊字符和數字。 “PG-13”對“分級”有效,但對於“分類”無效。
-
Range
特性將值限制在指定范圍內。 -
StringLength
特性使你能夠設置字符串屬性的最大長度,以及可選的最小長度。 -
從本質上來說,需要值類型(如
decimal
、int
、float
、DateTime
),但不需要[Required]
特性。
讓 ASP.NET Core 強制自動執行驗證規則有助於提升你的應用的可靠性。 同時它能確保你無法忘記驗證某些內容,並防止你無意中將錯誤數據導入數據庫。
驗證錯誤 UI
運行應用並導航到電影控制器。
點擊“新建”連接添加新電影 的鏈接。 使用無效值填寫表單。 當 jQuery 客戶端驗證檢測到錯誤時,會顯示一條錯誤消息。
備注
可能無法在小數字段中輸入十進制逗號。 若要使 jQuery 驗證支持使用逗號(“,”)表示小數點的的非英語區域設置,以及支持非美國英語日期格式,必須執行使應用全球化的步驟。 有關添加十進制逗號的說明,請參閱 GitHub 問題 4076。
請注意表單如何自動呈現每個包含無效值的字段中相應的驗證錯誤消息。 客戶端(使用 JavaScript 和 jQuery)和服務器端(若用戶禁用 JavaScript)都必定會遇到這些錯誤。
明顯的好處在於不需要在 MoviesController
類或 Create.cshtml 視圖中更改單個代碼行來啟用此驗證 UI 。 在本教程前面創建的控制器和視圖會自動選取驗證規則,這些規則是通過在 Movie
模型類的屬性上使用驗證特性所指定的。 使用 Edit
操作方法測試驗證后,即已應用相同的驗證。
存在客戶端驗證錯誤時,不會將表單數據發送到服務器。 可通過使用 Fiddler 工具或 F12 開發人員工具在 HTTP Post
方法中設置斷點來對此進行驗證。
驗證工作原理
你可能想知道在不對控制器或視圖中的代碼進行任何更新的情況下,驗證 UI 是如何生成的。 下列代碼顯示兩種 Create
方法。
// GET: Movies/Create public IActionResult Create() { return View(); } // POST: Movies/Create [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Create( [Bind("ID,Title,ReleaseDate,Genre,Price, Rating")] Movie movie) { if (ModelState.IsValid) { _context.Add(movie); await _context.SaveChangesAsync(); 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 的情況下仍然可以進行完整的驗證。
以下圖片顯示如何在 FireFox 瀏覽器中禁用 JavaScript。
以下圖片顯示如何在 Chrome 瀏覽器中禁用 JavaScript。
禁用 JavaScript 后,發布無效數據並單步執行調試程序。
Create.cshtml 視圖模板的一部分在以下標記中顯示 :
<h4>Movie</h4> <hr /> <div class="row"> <div class="col-md-4"> <form asp-action="Create"> <div asp-validation-summary="ModelOnly" class="text-danger"></div> <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> @*Markup removed for brevity.*@
操作方法使用上述標記來顯示初始表單,並在發生錯誤時重新顯示此表單。
輸入標記幫助程序使用 DataAnnotations 特性,並在客戶端上生成 jQuery 驗證所需的 HTML 特性。 驗證標記幫助程序用於顯示驗證錯誤。 有關詳細信息,請參閱驗證。
此方法真正好的一點是:無論是控制器還是 Create
視圖模板都不知道強制實施的實際驗證規則或顯示的特定錯誤消息。 僅可在 Movie
類中指定驗證規則和錯誤字符串。 這些相同的驗證規則自動應用於 Edit
視圖和可能創建用於編輯模型的任何其他視圖模板。
需要更改驗證邏輯時,可以通過將驗證特性添加到模型在同一個位置實現此操作。(在此示例中為 Movie
類)。 無需擔心對應用程序的不同部分所強制執行規則的方式不一致 - 所有驗證邏輯都將定義在一個位置並用於整個應用程序。 這使代碼非常簡潔,並且更易於維護和改進。 這意味着對 DRY 原則的完全遵守。
使用 DataType 特性
打開 Movie.cs 文件並檢查 Movie
類 。 除了一組內置的驗證特性,System.ComponentModel.DataAnnotations
命名空間還提供格式特性。 我們已經在發布日期和價格字段中應用了 DataType
枚舉值。 以下代碼顯示具有適當 DataType
特性的 ReleaseDate
和 Price
屬性。
[Display(Name = "Release Date")] [DataType(DataType.Date)] public DateTime ReleaseDate { get; set; } [Range(1, 100)] [DataType(DataType.Currency)] public decimal Price { get; set; }
DataType
屬性僅提供相關提示來幫助視圖引擎設置數據格式(並提供元素/屬性,例如向 URL 提供 <a>
和向電子郵件提供 <a href="mailto:EmailAddress.com">
)。 可以使用 RegularExpression
特性驗證數據的格式。 DataType
屬性用於指定比數據庫內部類型更具體的數據類型,它們不是驗證屬性。 在此示例中,我們只想跟蹤日期,而不是時間。 DataType
枚舉提供了多種數據類型,例如日期、時間、電話號碼、貨幣、電子郵件地址等。 應用程序還可通過 DataType
特性自動提供類型特定的功能。 例如,可以為 DataType.EmailAddress
創建 mailto:
鏈接,並且可以在支持 HTML5 的瀏覽器中為 DataType.Date
提供日期選擇器。 DataType
特性發出 HTML 5 data-
(讀作 data dash)特性供 HTML 5 瀏覽器理解。 DataType
特性不提供任何驗證 。
DataType.Date
不指定顯示日期的格式。 默認情況下,數據字段根據基於服務器的 CultureInfo
的默認格式進行顯示。
DisplayFormat
特性用於顯式指定日期格式:
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] public DateTime ReleaseDate { get; set; }
ApplyFormatInEditMode
設置指定在文本框中顯示值以進行編輯時也應用格式。 (你可能不想為某些字段執行此操作 — 例如對於貨幣值,你可能不希望文本框中的貨幣符號可編輯。)
可以單獨使用 DisplayFormat
特性,但通常建議使用 DataType
特性。 DataType
特性傳達數據的語義而不是傳達如何在屏幕上呈現數據,並提供 DisplayFormat 不具備的以下優勢:
-
瀏覽器可啟用 HTML5 功能(例如顯示日歷控件、區域設置適用的貨幣符號、電子郵件鏈接等)
-
默認情況下,瀏覽器將根據區域設置采用正確的格式呈現數據。
-
DataType
特性使 MVC 能夠選擇正確的字段模板來呈現數據(如果DisplayFormat
由自身使用,則使用的是字符串模板)。
備注
jQuery 驗證不適用於 Range
屬性和 DateTime
。 例如,以下代碼將始終顯示客戶端驗證錯誤,即便日期在指定的范圍內:
[Range(typeof(DateTime), "1/1/1966", "1/1/2020")]
需要禁用 jQuery 日期驗證才能使用具有 DateTime
的 Range
特性。 通常,在模型中編譯固定日期是不恰當的,因此不推薦使用 Range
特性和 DateTime
。
以下代碼顯示組合在一行上的特性:
public class Movie { public int Id { get; set; } [StringLength(60, MinimumLength = 3)] public string Title { get; set; } [Display(Name = "Release Date"), DataType(DataType.Date)] public DateTime ReleaseDate { get; set; } [RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$"), Required, StringLength(30)] public string Genre { get; set; } [Range(1, 100), DataType(DataType.Currency)] [Column(TypeName = "decimal(18, 2)")] public decimal Price { get; set; } [RegularExpression(@"^[A-Z]+[a-zA-Z0-9""'\s-]*$"), StringLength(5)] public string Rating { get; set; } }
在本系列的下一部分中,我們將回顧應用,並對自動生成的 Details
和 Delete
方法進行一些改進。