asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证
在前面的文章中我們曾經涉及到ControllerActionInvoker類GetParameterValue方法中有這么一句代碼:
? ?ModelBindingContext bindingContext = new ModelBindingContext() {
 ? ? ? ? ? ? ? ? FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
 ? ? ? ? ? ? ??? ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
 ? ? ? ? ? ? ? ??ModelName = parameterName,
? ? ? ? ? ? ? ? ModelState = controllerContext.Controller.ViewData.ModelState,
 ? ? ? ? ? ? ? ? PropertyFilter = propertyFilter,
 ? ? ? ? ? ? ? ? ValueProvider = valueProvider
 ? ? ? ? ? ? };
這里的PropertyFilter屬性表示在綁定的時候參數是否需要綁定數據,為true表示綁定數據,ValueProvider 屬性表示什么就很明白,ModelName 為綁定信息的Prefix屬性或則是我們的參數名。同時我們還知道ModelMetadataProviders.Current默認就是DataAnnotationsModelMetadataProvider。而DataAnnotationsModelMetadataProvider的GetMetadataForType方法具體實現是在 其父類AssociatedMetadataProvider中實現的:
? public override ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType) {
 ? ? ? ? ? ? if (modelType == null) {
 ? ? ? ? ? ? ? ? throw new ArgumentNullException("modelType");
 ? ? ? ? ? ? }
 ? ? ? ? ? ? IEnumerable<Attribute> attributes = GetTypeDescriptor(modelType).GetAttributes().Cast<Attribute>();
 ? ? ? ? ? ? ModelMetadata result = CreateMetadata(attributes, null /* containerType */, modelAccessor, modelType, null /* propertyName */);
 ? ? ? ? ? ? ApplyMetadataAwareAttributes(attributes, result);
 ? ? ? ? ? ? return result;
 ? ? ? ? }
? ? IEnumerable<Attribute> attributes = GetTypeDescriptor(modelType).GetAttributes().Cast<Attribute>();這句查找當前modelType的所有特性(這里的modelType主要是自定義的那些強類型,如果是內置類型就沒有意義了)。
? ModelMetadata result = CreateMetadata(attributes, null /* containerType */, modelAccessor, modelType, null /* propertyName */);這句是真正創建ModelMetadata 的地方,在DataAnnotationsModelMetadataProvider類中從寫了。
ApplyMetadataAwareAttributes(attributes, result);這個方法就是給result設置相應的屬性,具體的實現是通過調用IMetadataAware實例的OnMetadataCreated方法。默認有AdditionalMetadataAttribute、AllowHtmlAttribute實現了IMetadataAware接口。
controllerContext.Controller.ViewData.ModelState默認返回的是一個ModelStateDictionary(private readonly ModelStateDictionary _modelState),
默認情況下ModelState里面是沒有任何元素的。
由前面的文章我們知道,默認的強類型參數如:
[HttpPost]
 ??????? public ActionResult Index(UserInfo Info)
 ??????? {
 ??????????? return View(Info);
 ??????? }
這個Info參數的綁定都是走的BindComplexModel->BindComplexElementalModel方法。
?internal void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) {
 ??????????? // need to replace the property filter + model object and create an inner binding context
 ??????????? ModelBindingContext newBindingContext = CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model);
 ??????????? // validation
 ??????????? if (OnModelUpdating(controllerContext, newBindingContext)) {
 ??????????????? BindProperties(controllerContext, newBindingContext);
 ??????????????? OnModelUpdated(controllerContext, newBindingContext);
 ??????????? }
 ??????? }
?? ModelBindingContext newBindingContext = CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model);這句是創建新的ModelBindingContext,和現有的ModelBindingContext有何不同了,其中ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, bindingContext.ModelType),這里的() => mode究竟有什么影響了,在這個東東對應modelAccessor參數,在ModelMetadata中有一個Model屬性。
? public object Model {
 ??????????? get {
 ??????????????? if (_modelAccessor != null) {
 ??????????????????? _model = _modelAccessor();
 ??????????????????? _modelAccessor = null;
 ??????????????? }
 ??????????????? return _model;
 ??????????? }
 ??????????? set {
 ??????????????? _model = value;
 ??????????????? _modelAccessor = null;
 ??????????????? _properties = null;
 ??????????????? _realModelType = null;
 ??????????? }
 ??????? }
同時新的ModelBindingContext的PropertyFilter有所改變,
?Predicate<string> newPropertyFilter = (bindAttr != null)
 ??????????????? ? propertyName => bindAttr.IsPropertyAllowed(propertyName) && bindingContext.PropertyFilter(propertyName)
 ??????????????? : bindingContext.PropertyFilter;
現在新的ModelBindingContext已經創建。
現在剩下的
if (OnModelUpdating(controllerContext, newBindingContext)) {
 ??????????????? BindProperties(controllerContext, newBindingContext);
 ??????????????? OnModelUpdated(controllerContext, newBindingContext);
 ??????????? }
這幾句的意思也很好明白,?? BindProperties(controllerContext, newBindingContext)這是真正綁定數據的地方,綁定數據前后都可以調用相應的方法一個做預處理,一個做后置處理。默認OnModelUpdating直接返回true。BindProperties處理就比較復雜了。
?private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) {
 ??????????? IEnumerable<PropertyDescriptor> properties = GetFilteredModelProperties(controllerContext, bindingContext);
 ??????????? foreach (PropertyDescriptor property in properties) {
 ??????????????? BindProperty(controllerContext, bindingContext, property);
 ??????????? }
 ??????? }
首先需要獲取那些屬性需要綁定,然后在循環一次綁定每個屬性。
其中GetFilteredModelProperties的實現如下:
? protected IEnumerable<PropertyDescriptor> GetFilteredModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) {
 ??????????? PropertyDescriptorCollection properties = GetModelProperties(controllerContext, bindingContext);
 ??????????? Predicate<string> propertyFilter = bindingContext.PropertyFilter;
 ??????????? return from PropertyDescriptor property in properties
 ?????????????????? where ShouldUpdateProperty(property, propertyFilter)
 ?????????????????? select property;
 ??????? }
首先獲取類型的所有屬性描述集合PropertyDescriptorCollection,然后一次過濾調我們不需要綁定的屬性。過濾條件的實現是ShouldUpdateProperty方法中。
??? private static bool ShouldUpdateProperty(PropertyDescriptor property, Predicate<string> propertyFilter) {
 ??????????? if (property.IsReadOnly && !CanUpdateReadonlyTypedReference(property.PropertyType)) {
 ??????????????? return false;
 ??????????? }
 ??????????? // if this property is rejected by the filter, move on
 ??????????? if (!propertyFilter(property.Name)) {
 ??????????????? return false;
 ??????????? }
 ??????????? // otherwise, allow
 ??????????? return true;
 ??????? }
 CanUpdateReadonlyTypedReference這個方法很簡單,通過property.PropertyType是值類型、數組、string就返回true。BindProperty的實現就比較復雜了。
?public IDictionary<string, ModelMetadata> PropertyMetadata {
 ??????????? get {
 ??????????????? if (_propertyMetadata == null) {
 ??????????????????? _propertyMetadata = ModelMetadata.Properties.ToDictionary(m => m.PropertyName, StringComparer.OrdinalIgnoreCase);
 ??????????????? }
 ??????????????? return _propertyMetadata;
 ??????????? }
 ??????? }
而ModelMetadata的Properties屬性定義如下:
?public virtual IEnumerable<ModelMetadata> Properties {
 ??????????? get {
 ??????????????? if (_properties == null) {
 ??????????????????? _properties = Provider.GetMetadataForProperties(Model, RealModelType).OrderBy(m => m.Order);
 ??????????????? }
 ??????????????? return _properties;
 ??????????? }
 ??????? }
GetMetadataForProperties的實現是在AssociatedMetadataProvider類中實現的,循環RealModelType類型的每個屬性,每個屬性都會創建一個ModelMetadata,創建ModelMetadata的方法還是調用CreateMetadata實現的。所以我們知道ModelBindingContext的PropertyMetadata屬性是一個字典集合,key就是屬性名,value為一個ModelMetadata。
現在回到BindProperty方法中,它主要是獲取屬性綁定名稱,通過屬性類型獲取IModelBinder實例,同過bindingContext獲取屬性對應的ModelMetadata實例,進而創建新的ModelBindingContext實例,從而調用新的IModelBinder實例BindModel方法,獲取屬性對應的值,最后設置屬性對應的值。設置屬性對應的值是用過SetProperty方法來實現的。這個方法的代碼有點多,實際上很多都不執行的。
現在屬性都綁定完了,讓我們回到BindComplexElementalModel方法中來,該調用OnModelUpdated方法了:
protected virtual void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) {Dictionary<string, bool> startedValid = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);foreach (ModelValidationResult validationResult in ModelValidator.GetModelValidator(bindingContext.ModelMetadata, controllerContext).Validate(null)) {string subPropertyName = CreateSubPropertyName(bindingContext.ModelName, validationResult.MemberName);if (!startedValid.ContainsKey(subPropertyName)) {startedValid[subPropertyName] = bindingContext.ModelState.IsValidField(subPropertyName);}if (startedValid[subPropertyName]) {bindingContext.ModelState.AddModelError(subPropertyName, validationResult.Message);}}}
這個方法意思很簡單,驗證數據的有效性。我們先看ModelStateDictionary的IsValidField方法是如何實現的:
?return DictionaryHelpers.FindKeysWithPrefix(this, key).All(entry => entry.Value.Errors.Count == 0);是否有錯誤信息,有表示沒有通過驗證,沒有通過的驗證記錄相應的驗證信息。ModelStateDictionary的AddModelError方法:
public void AddModelError(string key, string errorMessage) {
 ??????????? GetModelStateForKey(key).Errors.Add(errorMessage);
 ??????? }
我們知道每一個key對應一個ModelState,這個方法就是把錯誤信息寫到ModelState對應的Errors屬性里面。
下面我們來看看究竟是如何驗證數據的。
首先ModelValidator.GetModelValidator方法返回的是一個CompositeModelValidator實例,實際上的驗證是調用的CompositeModelValidator的Validate方法:
public override IEnumerable<ModelValidationResult> Validate(object container) {bool propertiesValid = true;foreach (ModelMetadata propertyMetadata in Metadata.Properties) {foreach (ModelValidator propertyValidator in propertyMetadata.GetValidators(ControllerContext)) {foreach (ModelValidationResult propertyResult in propertyValidator.Validate(Metadata.Model)) {propertiesValid = false;yield return new ModelValidationResult {MemberName = DefaultModelBinder.CreateSubPropertyName(propertyMetadata.PropertyName, propertyResult.MemberName),Message = propertyResult.Message};}}}if (propertiesValid) {foreach (ModelValidator typeValidator in Metadata.GetValidators(ControllerContext)) {foreach (ModelValidationResult typeResult in typeValidator.Validate(container)) {yield return typeResult;}}}}整個驗證分類2部分一部分驗證屬性,一部分驗證類型,先驗證屬性,如果屬性驗證沒有通過則直接返回驗證結果。其中ModelMetadata的GetValidators的實現如下:return ModelValidatorProviders.Providers.GetValidators(this, context);ModelValidatorProviders的定義如下:
public static class ModelValidatorProviders {private static readonly ModelValidatorProviderCollection _providers = new ModelValidatorProviderCollection() {new DataAnnotationsModelValidatorProvider(),new DataErrorInfoModelValidatorProvider(),new ClientDataTypeModelValidatorProvider()};public static ModelValidatorProviderCollection Providers {get {return _providers;}}}所以這里的GetValidators實際上就是調用Providers里面的每個GetValidators方法,這里我們可以添加自己驗證ModelValidatorProvider,ModelValidatorProviders.Providers.Add(new xxxx());
這里驗證結束后,我們的參數綁定也就結束了。
相信大家現在多我們自定義數據類型的綁定已經有一個基本的了解了吧。
轉載于:https://www.cnblogs.com/lonelyxmas/p/3813279.html
總結
以上是生活随笔為你收集整理的asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 以大多数人的努力程度之低,根本轮不到去拼
- 下一篇: WPF学习系列之二 (依赖项属性)
