在《ASP.NET MVC以ModelValidator為核心的Model驗證體系: ModelValidator》中我們介紹了ASP.NET MVC用於Model驗證的四種ModelValidator,那麼這些ModelValidator是如何被創建的呢?ASP.NET MVC的很多組件(比如ModelBinder和Filter)都采用了基於Provider的提供機制,這篇文章為你講述這些ModelValidator對應的ModelValidatorProvider。
一、ModelValidatorProvider
我們通過注冊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
上面我們提到過的針對數據標注特性驗證方式的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的提供。
三、ClientDataTypeModelValidatorProvider
針對數值和日期類型客戶端驗證的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: }
四、DataErrorInfoModelValidatorProvider
兩個具體的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: }