驗證是ASP.NET MVC開發中一個非常重要的環節,包括客戶端和服務端驗證。幸好,MVC提供了非常簡便的數據注解(Data Annotations)來幫助我們進行這項工作。
1.驗證性的數據注解
MVC本身內置了一些常用的數據注解,像是Required,DisplayName等等,我會在下面一一講解。
最常用的就是Required,像是下面這樣:
使用Required可以指定錯誤消息:
[Required(ErrorMessage = "First Name is required")] public string FirstName { get; set; }
使用Required是不夠的,我們還需要規定用戶的輸入限制,像是字符串,就常有長度的限制,這時我們就可以利用StringLength。像是這樣:
[Required(ErrorMessage = "First Name is required")] [StringLength(160)] public string FirstName{ get; set;}
它規定了我們輸入的最大長度是160個字符。如果想要規定最小長度,我們可以這樣寫:
[Required(ErrorMessage = "First Name is required")] [StringLength(160, MinimumLength = 3)] public string FirstName { get; set; }
實際的效果如圖:
字符串的要求是長度,而數字的要求則是數據范圍范圍:
[Range(0.01, 100.0, ErrorMessage = "Price must be between 0.01 and 100.0")] public decimal Price { get; set; }
Range()是一個雙閉區間,就是說,包含0.01和100.0。
最后一個要講的,就是RegularExpression。
我們可以這樣使用:
[Required(ErrorMessage = "Email is required")] [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+[A-Za-z]{2,4}", ErrorMessage = "Email is not valid")] [DataType(DataType.EmailAddress)] public string Email { get; set; }
通過上面,我們可以知道,所有用於驗證用的數據注解都可以自定義自己的錯誤消息。
2.顯示性的數據注解
我們都注意到,變量FirstName在頁面上的實際顯示是:First Name。這樣做才是符合實際需要的,要想這樣做,我們就必須利用DisplayName:
[Required(ErrorMessage = "First Name is required")] [DisplayName("First Name")] [StringLength(160, MinimumLength = 3)] public string FirstName { get; set; }
同樣是為屬性定義說明,還有另一種注解:Dispaly,它的作用並不僅僅是指定顯示的內容,甚至能夠指定顯示的順序,像是這樣:
[Required(ErrorMessage = "First Name is required")] [Display(Name = "First Name", order = 15000)] [StringLength(160)] public string FirstName { get; set; }
order的默認參數是10000,如果其他變量並沒有設置order值,FirstName就排在其他變量后面。但實際上是不用這么麻煩的,如果不指定該值,就會按照變量聲明的順序排列。
Display在用於屬性的顯示名稱上,具有更高的優先級。
DisplayName專門用於定義屬性的顯示名稱,但是Display並不僅僅是如此。它的內容非常豐富,有些在這里不好講,而且本人是MVC新手,所以還請感興趣的同學自己查一下相關資料。
如果對數據庫有所了解的話,就會知道,數據庫需要key值用於搜索相應的元組。MVC經常與數據庫打交道,所以我們的模型中經常需要定義一個或幾個名稱中包含有Id的作為key的屬性,像是OrderId之類的,但是我們在顯示的時候又不想將這些屬性顯示出來,但正如我上面所講的,默認是會將該模型的所有屬性都顯示出來。那該怎么辦呢?就是對顯示隱藏,我們可以使用HiddenInput。
像是這樣:
public class Person{ [HiddenInput] public string Name { get; set; } [HiddenInput(DisplayValue = false)] public string Sex { get; set; } public int Age { get; set; } }
然后是控制器:
public ActionResult Index(){ Person person = new Person() { Name = "Jos", Sex = "man", Age = 18 }; return View(person); }
實際的效果如圖:
我們可以看到,"Sex"完全被隱藏起來了!
使用了HiddenInput,默認情況下屬性會以只讀形式顯示出來,像是上面的Name,要想完全隱藏,就得將DisplayValue設置為false。
我們可以來看看使用了HiddenInput的HTML:
<div class = "editor-label"><label for = "Name">Name</label></div> <div class = "editor-label">Jos<input id = "Name" name = "Name" type = "hidden" value = "Jos"/></div> <input id = "Sex" name = "Sex" type = "hidden" value = "man"/> <div class = "editor-label"><label for = "Age">Age</label></div> <div class = "editor-field"? <input class = "text-box single-line" id = "Age" name = "Age" type = "text" value = "18"/> <div>
這些只要了解就好,MVC會自動幫我們處理。
我們有時候會想,這樣做是好的嗎?畢竟,MVC會幫我們自動生成HTML文件,包括一些本該是由程序員自己設置的標記。但必須承認,這樣讓程序員從千篇一律的編碼中解放出來,讓我們能夠將關注點放在其他更值得關注的方面上,而且,我們程序員依然對生成的HTML文件具有很大的控制權。
特別強調一下,HiddenInput的命名空間是System.Web.Mvc。
同樣是隱藏屬性在HTML上的顯示,我們還可以使用ScaffoldColumn特性。
使用ScaffoldColumn並不是為屬性設置type = "hidden",它是直接將該屬性從基架中刪除。我們知道,MVC可以通過預定義模板來自動生成HTML,這種方式就是基架(Scaffolding)。ScaffoldColumn表示存在於基架中並最終呈現在HTML中的字典。
像是這樣:
[ScaffoldColumn(false)] public int OrderId { get; set; }
即使從基架中將該屬性刪除,模型綁定器仍然會試圖為該屬性賦值,這樣就為典型的攻擊“重復提交”提供了機會。黑客們可以試圖向我們的網頁中發送"OrderId = 100"這樣的字段,而我們的模型綁定器會為該屬性賦值並且有可能賦為黑客提供的值。要想防止這種攻擊,我們可以利用Bind特性。
Bind特性可以選擇模型綁定器要綁定的值。像是這樣:
[Bind(Include = "Name, Comment")] public class Review{ public int ReviewId{ get; set;} public int ProductId{ get; set;} publc string Name{ get; set;} public string Comment{ get; set;} }
這樣模型綁定器就只綁定Name和Comment屬性。
當然,我們也可以選擇不綁定的屬性:
[Bind(Exclude = "OrderId")] public partial class Order { [ScaffoldColumn(false)] public int OrderId { get; set; } [ScaffoldColumn(false)] public string UserName { get; set; } }
這樣,上面所講的“重復提交”攻擊就無法發揮作用了。但是必須注意,使用"Include"的白名單比起使用"Exclude"的黑名單更加安全,因為我們永遠也不知道黑客會用怎樣的方式來攻擊我們,但確定的是,我們可以知道哪些被綁定的屬性是不會造成太大的危害的。
使用白名單還是黑名單還是得看具體情況,畢竟有時候我們需要綁定的屬性非常多,如果采用白名單的方式,光是數據注解這里可能就要超過了我們該模型類的代碼總量了。
Bind既可以用於模型,也可以用於控制器操作的參數,但更多時候還是用在模型上。
提到模型綁定器,這里就必須得說一下屬性的更新問題。模型綁定器會更新被綁定的屬性的值,但有些值我們可能希望它永遠都不要被更新,那么我們就可以使用ReadOnly特性。
像是這樣:
[ReadOnly(true)] public decimal Total{ get; set;}
模型綁定器就不會更新它的值,雖然實際運行的時候,我們的確可以在文本框里修改它的值,但是該值並不會被用來更新該屬性的值。
ReadOnly的命名空間是在using System.ComponentModel。
既然提到可讀,那么一定存在可寫。是的,在System.ComponentModel.DataAnnotations中就有一個Editable。
它的使用方式和ReadOnly完全一樣,但我們會很好奇:如果同時將這兩個注解用在同一個屬性上,會怎么樣呢?事實上,Editable擁有更高的優先級。
接下來講到的數據注解就真的非常重要。所有顯示性的數據注解,其實都是提供給HTML 輔助方法和ASP.NET MVC運行時的其他組件使用,這些都是靠模型元數據提供器來負責收集。所以,就有一種數據注解能夠為運行時提供關於屬性的特定用途。這就是DataType。
我們會在用戶的登陸界面中要求用戶輸入登陸密碼,但是又必須保證,這些密碼不會顯示出來以免被別人看到,這時,DataType就發揮作用了:
[Required] [DataType(DataType.Password)] [DisplayName("Password")] public string Password { get; set; }
從上面可以看出,DataType所謂的數據類型,並不是我們常說的int,float等,事實上,DataType本身也有一個可支持的數據類型枚舉:
public enum DataType{ Custom, DateTime, Date, Time, Duration,
PhoneNumber, Currency, Text, Html, MultilineText,
EmailAddress, Password, Url, ImageUrl, CreditCard,
PostalCode, Upload }
DataType實際上是一個驗證特性,它繼承自ValidationAttribute,但我之所以放在這里講,是因為所謂的"顯示"性,是指它的作用效果,像DataType的作用效果就是為它內置的數據類型提供不同的顯示效果。
當然,我們還可以自定義自己的數據類型,不過這種情況下我們就必須自定義該數據類型的DisplayFormat。這些都在DataType的源碼中:
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple=false)] public class DataTypeAttribute : ValidationAttribute{ public DataTypeAttribute(DataType dataType); public DataTypeAttribute(string customDataType); public virtual string GetDataTypeName(); public override bool IsValid(object value); public string CustomDataType { get; } public DataType DataType { get; } public DisplayFormatAttribute DisplayFormat { get; } }
我們有時候想要對輸出進行格式化設置,這時我們就可以利用DisplayFormat。
像是這樣:
[Required(ErrorMessage = "Price is required")] [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:c}")] [Range(0.01, 100.0, ErrorMessage = "Price must be between 0.01 and 100.0")] public decimal Price { get; set; }
就會有這樣的效果:
話說,中文版的VS2012莫非真的是入鄉隨俗,我看到外國的教程都是美元標記,但這里卻是RMB標記!但這點確實真的很重要,畢竟,在中國發布的網站價格標注的要是美元的話,那還真的是沒有多少人會買了。
這里必須注意,ApplyFormatInEditMode的默認值是false,之所以這樣,是因為我們模型綁定器無法解析格式化的值。
查看DisplayFormat的源碼,我們就可以發現,它還有兩個個布爾類型的屬性:HtmlEncode和ConvertEmptyStringToNull,前者表示是否需要對目標內容進行HTML編碼,默認下是true(這樣是為了安全性的考慮),后者表示是否將傳入的空字符串轉換成Null。
DisplayFormat還有一個string屬性:NullDisplayText,它表示針對空值(Null)對象的顯示文本。
同樣的道理,因為DataType也對應着一個DisplayFormatAttribute,所以當這兩個特性同時應用在相同的元素上,后者具有更高級的優先級。
最后一個要講的數據注解,因為不知道要放在哪里,但提到模板的話,就還是放在這里好了。它就是UIHintAttribute。
我們知道,HTML輔助方法會幫我們選擇基於Model的模板方法。所謂的模板方法,指的是我們在通過調用這些方法來將Model的數據顯示在View中時,采用默認或者指定的模板來決定最終呈現在瀏覽器中的HTML。這點在我們使用MVC的時候就已經察覺了:模板名稱對應具體的Model元數據。所以,通過元數據,我們可以自定義要選擇的模板。像是這樣:
public class Person{ [UIHint("Template A")] public string Name{ get; set;} [UIHint("Template B", "Mvc")] [UIHint("Template A")]
public string Sex{ get; set;} }
它有兩個構造函數:
public UIHintAttribute(string uiHint); public UIHintAttribute(string uiHint, string presentationLayer);
對於最后一個屬性Sex,最終采用的是Template B,因為它的presentationLayer的值為Mvc。
本人完全是MVC新手,所講的均是自己的理解,如果有說得不對的地方,還請各位大神指出。