對於Web開發人員來講,用戶輸入驗證一直是一個挑戰。不只在客戶端瀏覽器中須要執行驗證邏輯,在服務器端也須要執行。若是以爲驗證是使人望而生畏的繁雜雜事,ASP.NET MVC框架提供了數據註解的方式幫助咱們處理這些雜事。html
數據註解特性定義在名稱空間System.ComponentModel.DataAnnotations中,它們提供了服務器端驗證的功能,當在模型的屬性上使用這些特性時,框架也支持客戶端驗證。在名稱空間DataAnnotations中,有4個特性能夠用來應對通常的驗證場合。下面從Required特性開始對它們逐一介紹。web
這個特性表必須的、不能爲空的。用於不爲空校驗。正則表達式
示例1 瀏覽器
[Required(ErrorMessage="姓名不得爲空")] 安全 public string Name { get; set; } //姓名服務器 |
這個特性能夠驗證字符串長度,通常用來驗證屬性的最大長度,也支持同時設置最小長度。如示例2所示app
示例2 框架
[DisplayName("密碼")] 編輯器 [StringLength(20,ErrorMessage = "{0}不能超過{1}個字符")] ide public string Password { get; set; }
[DisplayName("密碼")] [StringLength(20,MinimumLength=6, ErrorMessage = "{0}長度必須在{2}和{1}之間")] public string Password { get; set; }
|
使用ErrorMessage屬性,通常都會在字符串中使用佔位符,上述代碼中,{0}對應屬性的名稱,{1}對應maximum數據,{2}對應minimum數據。
Range 驗證特性能夠驗證數字(整數和浮點數)、時間等類型的範圍,對應的構造函數以下所示。
public RangeAttribute(int minimum, int maximum);
public RangeAttribute(double minimum, double maximum);
public RangeAttribute(Type type, string minimum, string maximum);
這三個構造函數主要用來驗證時間等特殊類型。如示例3所示。
示例3
[DisplayName("年齡")] [Range(18,35,ErrorMessage ="{0}必須在{1}和{2}之間")] public int Age { get; set; } [DisplayName("生日")] [Range(typeof(DateTime),"2001-01-01","2019-12-31",ErrorMessage = "{0}必須在{1}和{2}之間"] public DateTime? BornDate { get; set; } |
上述代碼中,{0}對應屬性的名稱,{1}對應minimum數據,{2}對應maximum數據。
使用Compare驗證特性通常用來驗證兩個屬性的值是否一致。如示例4所示。
示例4
public string Passowrd { get; set; } [Compare("Password",ErrorMessage ="兩次輸入的密碼必須一致")] public string PasswordConfig { get; set; } |
RegularExpression驗證特性的關鍵是正則表達式的編寫,如示例5所示。
示例5
[RegularExpression(@"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$", ErrorMessage = "{0}格式不正確")] public string Email { get; set; } |
同其它幾個驗證特性不一樣,Remote特性的命名空間是System.Web.Mvc。Remote特性利用服務器端的回調函數執行客戶端的驗證邏輯。如示例6所示。
示例6
//控制器代碼 public ActionResult CheckTelephone(string telephone) { if (telephone=="13636595489") { return Json("手機號"+telephone+ "已經存在", JsonRequestBehavior.AllowGet); } return Json(true, JsonRequestBehavior.AllowGet); } //實體類代碼 [System.Web.Mvc.Remote("CheckTelephone", "Default", ErrorMessage ="手機號碼已經存在")] public string Telephone { get; set; } |
ASP.NET MVC 框架支持根據模型自動生成客戶端驗證代碼或腳本,但必須知足如下條件:
<appSettings> <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" /> </appSettings> |
示例7
@using (Html.BeginForm()) { <div class="form-horizontal"> <div class="form-group"> @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" }) </div> </div> </div> } |
在示例7中,Html.EditorFor()方法和Html.TextBoxFor()方法相似,用來輸出input標籤(後面內容介紹區別)。的View視圖會生成以下代碼。
<form action="/Home/Login" method="post"> <div class="form-horizontal"> <div class="form-group"> <label class="control-label col-md-2" for="Name">姓名</label> <div class="col-md-10"> <input class="form-control text-box single-line" data-val="true" data-val-required="姓名不得爲空" id="Name" name="Name" type="text" value="" /> <span class="field-validation-valid text-danger" data-valmsg-for="Name" data-valmsg-replace="true"></span> </div> </div> </div> </form> |
示例7中,Html.ValidationMessageFor()和Html.ValidationMessage()擴展方法用來生成HTML的驗證消息。由HTML輔助方法默認生成的HTML都附加了符合HTML5標準的data-*屬性,這些屬性是根據模型自動生成的,引用的客戶端腳本結合這些屬性就構成了完整的客戶端驗證功能。
ASP.NET MV 還提供了 一些列視圖模板,來簡化示例8的複雜工做。在添加視圖時,在"添加視圖"對話框中的模板選項中選擇不一樣的模板。如圖8-1所示。
圖8-1 選擇視圖模板
系統提供了Create、Delete、Details、Edit 和 List 模板,選擇這些模板,系統會自動生成帶有驗證的、如示例8中所示的視圖代碼。在實際開發中,咱們只須要作適當的修改便可。
在模型中定義驗證規則後,ASP.NET MVC 在將數據映射到模型時,會自動應用模型類上的驗證規則。在驗證的過程當中,它會自動把驗證錯誤信息添加到ModelState數據字典,這時只須要讀取其IsVaild 屬性,就能夠判斷是否經過。
另外也可使用 ModeState 的 AddModelError()方法添加自定義的錯誤信息,用法如示例8所示。
示例8
public ActionResult RegisterUser(User user) { if (ModelState.IsValid) { UserManager manager = new UserManager(); if (!manager.Register(user)) { ModelState.AddModelError("doubleUser", "用戶名已使用,請從新輸入!"); return View("Register2", user); } else//註冊成功 { return Redirect("~/"); } } return View("Register2", user); } |
當服務器端驗證生效後,視圖中使用 Html.ValidationMessage()來輸出驗證信息,除此以外還有Html.ValidationSummary()用來顯示驗證信息的彙總信息。
除了 ASP.NET MVC 中提供驗證規則外,咱們還能夠自定義驗證規則。例如,咱們要求用戶名不容許是"admin"、"sa"等特殊字符串。
要實現自定義驗證規則,只要繼承 ValidationAttribute類,並重寫IsValid方法便可。如示例9所示。
示例9
public class UserNameAttribute:ValidationAttribute { protected override ValidationResult IsValid(object value, ValidationContext validationContext) { string[] keywords = { "admin", "sa" }; if (!keywords.Contains(value)) { return ValidationResult.Success; } else { return new ValidationResult("不容許使用關鍵字!"); } } } |
IsValid方法中的第一個參數 value 是要驗證的對象的值,ValidationContext參數,提供了不少可在IsValid返回發內部使用的信息,如模型類型、模型對象實例、用來驗證屬性的人性化顯示名稱以及其餘有用信息。
上面代碼中的問題在於硬編碼的錯誤提示消息那行代碼。使用數據註解的開發人員但願可使用ValidationAttribute的ErrorMessage屬性來自定義錯誤提示消息。同時還要與其餘驗證特性同樣,提供一個默認的錯誤提示消息(開發人員沒有提供自定義的錯誤提示消息時使用)而且還要利用驗證的屬性名稱生成錯誤提示消息。對示例9的完善如示例10所示。
示例10
public class UserNameAttribute:ValidationAttribute { public UserNameAttribute() :base("{0}不容許使用關鍵字!") { } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { string[] keywords = { "admin", "sa" }; if (!keywords.Contains(value)) { return ValidationResult.Success; } else { var errorMessage = FormatErrorMessage(validationContext.DisplayName); return new ValidationResult(errorMessage); } } } |
前面的代碼作了兩處改動:
FormatErrorMessage能夠確保咱們使用合適的錯誤提示消息字符串。這條代碼語句須要傳遞name屬性的值,這個值能夠經過validationContext參數的DisplayName屬性得到。構造完驗證邏輯後,就能夠將其應用到任何模型屬性上。
[UserName] public string Name { get; set; }
[UserName(ErrorMessage=" {0}使用了關鍵字!")] public string Name{get;set;} |
DisplayName用來在視圖中經過 Html.LabelFor()方法顯示屬性的名稱。如:
[DisplayName("姓名")] public string Name { get; set; } |
一樣是爲屬性定義說明,還有另外一種註解:Dispaly,它的做用並不只僅是指定顯示的內容,甚至可以指定顯示的順序。
[Display(Name="姓名",Order=15000)] public string Name { get; set; } |
order的默認參數是10000, Display在用於屬性的顯示名稱上,具備更高的優先級。
咱們的模型中常常須要定義一個名稱中包含有Id的做爲key的屬性,可是咱們在顯示的時候又不想將這些屬性顯示出來,那該怎麼辦呢?就是對顯示隱藏,咱們可使用HiddenInput。如示例11所示。
示例11
[HiddenInput] public int Id{ get; set; }
[HiddenInput(DisplayValue = false)] public int UserId { get; set; } |
使用了HiddenInput,默認狀況下屬性會以只讀形式顯示出來,像是上面的Name,要想徹底隱藏,就得將DisplayValue設置爲false。
隱藏屬性在HTML上的顯示,咱們還可使用ScaffoldColumn特性。使用ScaffoldColumn並非爲屬性設置type = "hidden",它是直接將該屬性從基架中刪除。如:
[ScaffoldColumn(false)] public int UserId{ get; set; } |
即便從基架中將該屬性刪除,模型綁定器仍然會試圖爲該屬性賦值,這樣就爲典型的攻擊"重複提交"提供了機會。要想防止這種攻擊,咱們能夠利用Bind特性。
Bind特性能夠選擇模型綁定器要綁定的值。像是這樣:
示例12
[Bind(Include = "Name, Email")] public class User { public int UserId{ get; set;} public string Name{ get; set;} public string Email{ get; set;} } |
這樣模型綁定器就只綁定Name和Email屬性。 固然,咱們也能夠選擇不綁定的屬性:
[Bind(Exclude ="UserId")] public class User { public int UserId{ get; set;} public string Name{ get; set;} public string Email{ get; set;} } |
這樣,上面所講的"重複提交"攻擊就沒法發揮做用了。可是必須注意,使用"Include"的白名單比起使用"Exclude"的黑名單更加安全,由於咱們永遠也不知道黑客會用怎樣的方式來攻擊咱們。
Bind既能夠用於模型,也能夠用於控制器操做的參數。
若是須要確保默認的模型綁定器不使用請求中的新值來更新屬性,可在屬性上添加ReadOnly特性。(並不會在Html代碼中生成 ReadOnly 屬性)
DataType特性可爲運行時提供關於屬性的特定用途信息。例如,String類型的屬性可應用於不少場合一一能夠保存e-mail地址、URL或是密碼。 如示例13所示。
示例13
DataType(DataType.Password) public string Password{ get; set; } |
除了 Password 類型外,還有Custom, DateTime, Date, Time, Duration, PhoneNumber, Currency, Text, Html, MultilineText, EmailAddress, Password, Url, ImageUrl, CreditCard,
PostalCode, Upload。
DisplayForat特性可用來處理屬性的各類格式化選項。如示例14所示。
示例14
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:c}")] public decimal Price { get; set; } |
上面的代碼可將模型的 Price 屬性值格式化爲貨幣值形式。ApplyFormatInEditMode參數的值默認是false,若是想把 Price 屬性格式化爲表單輸入元素,須要將屬性ApplyFormatInEditMode的值設置爲true。例如,當把模型中decimal類型的 Price 屬性值設置爲12.1時,將在視圖中看到¥12.10的輸出結果。
DisplayFormat還有一個string屬性:NullDisplayText,它表示針對空值(Null)對象的顯示文本。
UIHint特性給ASP.NET MVC運行時提供了一個模板名稱,以備調用模板輔助方法,如(DisplayFor和EditorFor)渲染時輸出使用。也能夠定義本身的模板輔助方法來重寫ASP.NET MVC的默認行爲。
Html.EditorFor()和 Html.DisplayFor()的特殊之處在於它支持模板功能。
示例15完成一個日曆模板,該模板能夠爲DateTime類型的模型屬性自動應用該模板。
步驟:
示例15
@model DateTime? @Html.TextBoxFor(m => m, "", new { @class = "Wdate", onfocus = "$(this).datepicker()" }) |
在視圖中,下面代碼將直接致使輸出一個jQuery日曆文本框。由於BornDate屬性是DateTime 類型,將自動應用這個模板。(在視圖中須要引入相關js文件)
@Html.EditorFor(m=>m.PublishDate)
若是不想讓每個DateTime編輯器都擁有DatePicker小部件,而是讓一少部分特定的編輯器擁有。此時,能夠把自定義的模板文件名改成"SpecialDateTime.cshtml",這樣框架就不會爲DateTime模型選擇該模板,除非指定該模板名稱。以下面代碼所示。
@Html.EditorFor(m=>m.PublishDate,"SpecialDateTime")
另外,也能夠在PublishDate屬性上使用UIHint特性來指定模板名稱:
[UIHint("SpecialDateTime")]
public DateTime? PublishDate { get;set;}
示例16完成將出版社顯示成下拉框的模板。
步驟:
示例16
@model BookShop.Models.Publisher
@Html.DropDownListFor(m=>m.Id,(SelectList)ViewData["publisher"],"---請選擇---") |
在示例16的代碼中,模板視圖對應的類型是BookShop.Models.Publisher,在視圖中下面的代碼將自動將出版社顯示爲下拉框列表。
@Html.EditorFor(model=>model.Publisher)
Html.DisplayFor()使用自定義模板的方式和 Html.EditorFor相似,區別在於將模板文件夾改成"DisplayTemplates"