ASP.NET MVC以ModelValidator為核心的Model驗證體係: ModelValidatorProvider
在《ASP.NET MVC以ModelValidator為核心的Model驗證體係: ModelValidator》中我們介紹了ASP.NET MVC用於Model驗證的四種ModelValidator,那麼這些ModelValidator是如何被創建的呢?ASP.NET MVC的很多組件(比如ModelBinder和Filter)都采用了基於Provider的提供機製,這篇文章為你講述這些ModelValidator對應的ModelValidatorProvider。[本文已經同步到《How ASP.NET MVC Works?》中]
目錄
一、ModelValidatorProvider
二、DataAnnotationsModelValidator
三、ClientDataTypeModelValidatorProvider
四、DataErrorInfoModelValidatorProvider
我們通過注冊ModelValidatorProvider來創建相應的ModelValidator,所有的ModelValidatorProvider直接或者間接地繼承類型ModelValidatorProvider。如下麵的代碼片斷所示,ModelValidator的提供實現在抽象方法GetValidators種,返回的是一個ModelValidator集合。
1: public abstract class ModelValidatorProvider
2: {
3: public abstract IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ModelBindingExecutionContext context);
4: }
由於ValueProvider提供的數據值僅限於簡單類型,所以針對複雜類型的Model綁定采用一個遞歸的過程對作為Model對象的所有屬性進行綁定。Model驗證可以看成是Model綁定的後續環節,它對綁定的數據實施驗證,所以Model驗證也是一個遞歸的過程,它采用基於屬性的驗證規則對綁定的屬性值實施驗證。GetValidators方法具有兩個參數,類型ModelMetadata的metadata參數用於或者相應的驗證規則,而參數context則是表示當前Model綁定上下文的ModelBindingExecutionContext對象。
上麵我們提到過的針對數據標注特性驗證方式的DataAnnotationsModelValidator對應的ModelValidatorProvider類型為DataAnnotationsModelValidatorProvider。如下麵的代碼片斷所示,DataAnnotationsModelValidatorProvider繼承自另一個抽象類型AssociatedValidatorProvider。
1: public class DataAnnotationsModelValidatorProvider : AssociatedValidatorProvider
2: {
3: //其他成員
4: public DataAnnotationsModelValidatorProvider();
5: protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context,IEnumerable<Attribute> attributes);
6: }
類型名稱AssociatedValidatorProvider中所謂的“關聯(Association)”實際上代表的是,即它根據從Model元數據中得到的用於定義驗證規則的特性列表來提供相應的ModelValidator。如下麵的代碼片斷所示,AssociatedValidatorProvider定義一個受保護的虛方法GetTypeDescriptor用於獲取指定類型的描述對象(其類型實現了接口ICustomTypeDescriptor)。被解析出來的關聯特性最終傳入抽象的GetValidators方法實現了對ModelValidator的提供,而DataAnnotationsModelValidatorProvider正是實現了這個方法來創建相應的DataAnnotationsModelValidator列表。
1: public abstract class AssociatedValidatorProvider : ModelValidatorProvider
2: {
3: protected virtual ICustomTypeDescriptor GetTypeDescriptor(Type type);
4: public sealed override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);
5: protected abstract IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes);
6: }
在被重寫的GetValidators方法中,如果當前Model元數據是基於某個屬性的(表示容器類型的ContainerType不會Null並且具有屬性名稱),在調用GetTypeDescriptor方法獲取容器類型描述對象,進而根據屬性類型得到用於描述屬性的PropertyDescriptor對象,最終通過該描述對象得到應用在對應屬性上的所有特性並調用抽象方法GetValidators返回基於屬性的ModelValidator列表。對於非屬性Model元數據,在直接調用GetTypeDescriptor方法得到Model類型描述對象,進而獲取應用在Model類型上的所有特性並傳入抽象方法GetValidators實現對針對Model類型的ModelValidator的提供。
針對數值和日期類型客戶端驗證的NumericModelValidator和DateModelValidator最終是通過具有如下定義的ClientDataTypeModelValidatorProvider來提供的。在GetValidators方法中,它會根據指定的Model元數據判斷是否屬於數值類型/DateTime類型,如果是則直接返回一個包含單個NumericModelValidator/DateModelValidator對象的ModelValidator集合。在這裏被視為數值的數據類型包括byte,、sbyte、short,、ushort、int、uint,long,、ulong、float、double,和decimal等。
1: public class ClientDataTypeModelValidatorProvider : ModelValidatorProvider
2: {
3: public ClientDataTypeModelValidatorProvider();
4: public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);
5: }
兩個具體的DataErrorInfoModelValidator,即DataErrorInfoClassModelValidator和DataErrorInfoPropertyModelValidator最終是通過具有如下定義的DataErrorInfoModelValidatorProvider來提供的。對於GetValidators的具體實現來說,如果Model類型實現了IDataErrorInfo接口,會基於製定的Model元數據和Controller上下文創建一個DataErrorInfoClassModelValidator對象置於返回的ModelValidtor集合中。對於基於屬性的Model元數據來說,如果其容器類型實現了IDataErrorInfo接口,該方法返回的ModelValidtor集合中還會包含一個基於指定Model元數據和Controller上下文創建的DataErrorInfoPropertyModelValidator對象。
1: public class DataErrorInfoModelValidatorProvider : ModelValidatorProvider
2: {
3: public DataErrorInfoModelValidatorProvider();
4: public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);
5: }
為了讓讀者更好地了解DataErrorInfoModelValidator的驗證規則,以及定義在DataErrorInfoPropertyModelValidator中針對它的提供機製,我們來演示一個簡單的實例。在通過Visual Studio的ASP.NET MVC項目模板創建的空Web應用中我們定義了如下一個實現了IDataErrorInfo接口的Contact類型。
1: public class Contact: IDataErrorInfo
2: {
3: public string Error
4: {
5: get { return "無效聯係人!";}
6: }
7: public string this[string columnName]
8: {
9: get
10: {
11: switch (columnName)
12: {
13: case "Name" : return "姓名是必需的!";
14: case "PhoneNo" : return "電話號碼格式錯誤!";
15: case "EmailAdderss" : return "無效的電子郵箱地址!";
16: default : return null;
17: }
18: }
19: }
20: public string Name { get; set; }
21: public string PhoneNo { get; set; }
22: public string EmailAdderss { get; set; }
23: }
然後創建了如下一個默認的HomeController類。在Action方法Index中,我們通過DataErrorInfoModelValidatorProvider根據Contact類型極其屬性的Model元數據創建了一個ModelValidator列表,然後使用這個列表中的每個具體的ModelValidator對一個Contact對象實施驗證,並將ModelValidator的類型和作為驗證結果的ModelValidationResult對象的ErrorMessage屬性呈現出來。
1: public class HomeController : Controller
2: {
3: public void Index()
4: {
5: Contact contact = new Contact();
6: ModelValidatorProvider validatorProvider = new DataErrorInfoModelValidatorProvider();
7: foreach (var validator in GetValidators(contact, validatorProvider))
8: {
9: var validationResults = validator.Validate(contact);
10: if (validationResults.Any())
11: {
12: Response.Write(validator.GetType().Name + "<br/>");
13: }
14: foreach(var validationResult in validationResults)
15: {
16: Response.Write(validationResult.Message + "<br>");
17: }
18: }
19: }
20:
21: private IEnumerable<ModelValidator> GetValidators(Contact contact, ModelValidatorProvider validatorProvider)
22: {
23: ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForType(() => contact, typeof(Contact));
24: foreach (var validator in validatorProvider.GetValidators(metadata, ControllerContext))
25: {
26: yield return validator;
27: }
28:
29: foreach (var propertyMetadata in metadata.Properties)
30: {
31: foreach (var validator in validatorProvider.GetValidators(propertyMetadata, ControllerContext))
32: {
33: yield return validator;
34: }
35: }
36: }
37: }
上麵的程序運行之後會在瀏覽器中呈現出如下所示的輸出結果,從中可以看到針對Contact類型的Model元數據創建的是一個DataErrorInfoClassModelValidator,而針對其屬性的Model元數據創建的則是一個DataErrorInfoPropertyModelValidator對象。前者對Contact對象本身實施驗證,並將Error屬性作為驗證結果的錯誤消息;後者針對應的屬性實施驗證,驗證結果的錯誤消息來源於將屬性名稱作為索引的值。
1: DataErrorInfoClassModelValidator
2: 無效聯係人!
3: DataErrorInfoPropertyModelValidator
4: 姓名是必需的!
5: DataErrorInfoPropertyModelValidator
6: 電話號碼格式錯誤!
7: DataErrorInfoPropertyModelValidator
8: 無效的電子郵箱地址!
ASP.NET MVC以ModelValidator為核心的Model驗證體係: ModelValidator
ASP.NET MVC以ModelValidator為核心的Model驗證體係: ModelValidatorProvider
ASP.NET MVC以ModelValidator為核心的Model驗證體係: ModelValidatorProviders
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-26 11:33:57