zoukankan      html  css  js  c++  java
  • asp.net mvc源码分析Action篇 DefaultModelBinder

    接着上篇 asp.net mvc源码分析-Controller篇 ValueProvider 现在我们来看看ModelBindingContext这个对象。

     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
                };

    一般情况下FallbackToEmptyPrefix 应该是true,默认parameterDescriptor.BindingInfo.Prefix为空。里面的ModelMetadata属性是ModelMetadata的一个实例。

    看看它的构造函数的定义,

      public ModelMetadata(ModelMetadataProvider provider, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) 这个类涉及到的东西很多,有些我现在也不是很明白,只知道他们是干什么的,所以这个类在这里只是简单的提一下而已。

    它有一个属性

        public virtual bool IsComplexType {
                get {
                    return !(TypeDescriptor.GetConverter(ModelType).CanConvertFrom(typeof(string)));
                }
            }
    看看当前参数对象能否转化为string对象,如果可以则是简单对象,否则则是复杂对象。

    这里的ModelMetadataProviders.Current是一个DataAnnotationsModelMetadataProvider实例。DataAnnotationsModelMetadataProvider继承于AssociatedMetadataProvider继承于AssociatedMetadataProvider继承于ModelMetadataProvider这里的调用GetMetadataForType来获取ModelMetadata,而真正创建ModelMetadata的是在DataAnnotationsModelMetadataProvider的CreateMetadata,该方法定义如下:

     protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) 

    该方法真正创建了一个DataAnnotationsModelMetadata实例,该类是ModelMetadata的子类。

    下面 我们来看看ModelState属性=controllerContext.Controller.ViewData.ModelState

    其中 ModelState非常简单

    [Serializable]
        public class ModelState {
            private ModelErrorCollection _errors = new ModelErrorCollection();
            public ValueProviderResult Value { get;  set;}
            public ModelErrorCollection Errors {get {  return _errors; }
            }

    而ViewDataDictionary的ModelState是一个ModelState的字典集合类ModelStateDictionary。该属性默认就只是一个实例里面没有ModelState。

    下面这句binder.BindModel(controllerContext, bindingContext)是真正绑定参数的地方,我们知道默认的binder是DefaultModelBinder,所以现在我们来看看你它的BindModel方法:

       public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
                if (bindingContext == null) {
                    throw new ArgumentNullException("bindingContext");
                }
    
                bool performedFallback = false;
    
                if (!String.IsNullOrEmpty(bindingContext.ModelName) && !bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) {
                    // We couldn't find any entry that began with the prefix. If this is the top-level element, fall back
                    // to the empty prefix.
                    if (bindingContext.FallbackToEmptyPrefix) {
                        bindingContext = new ModelBindingContext() {
                            ModelMetadata = bindingContext.ModelMetadata,
                            ModelState = bindingContext.ModelState,
                            PropertyFilter = bindingContext.PropertyFilter,
                            ValueProvider = bindingContext.ValueProvider
                        };
                        performedFallback = true;
                    }
                    else {
                        return null;
                    }
                }
    
                // Simple model = int, string, etc.; determined by calling TypeConverter.CanConvertFrom(typeof(string))
                // or by seeing if a value in the request exactly matches the name of the model we're binding.
                // Complex type = everything else.
                if (!performedFallback) {
                    bool performRequestValidation = ShouldPerformRequestValidation(controllerContext, bindingContext);
                    ValueProviderResult vpResult = bindingContext.UnvalidatedValueProvider.GetValue(bindingContext.ModelName, skipValidation: !performRequestValidation);
                    if (vpResult != null) {
                        return BindSimpleModel(controllerContext, bindingContext, vpResult);
                    }
                }
                if (!bindingContext.ModelMetadata.IsComplexType) {
                    return null;
                }
    
                return BindComplexModel(controllerContext, bindingContext);
            }
    

      这里面有一个判断 if (!String.IsNullOrEmpty(bindingContext.ModelName) && !bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName))  当然正常情况下
    bindingContext.ModelName是不为空的,bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)则是检查所有的ValueProvider中的所有keys是否有一个包含Action中的参数名,一般我们用的最多的是ChildActionValueProviderFactory、FormValueProviderFactory、QueryStringValueProviderFactory,用ChildActionValueProviderFactory一般是因为我们经常会有这样的代码Html.RenderAction,那么正常情况下ChildActionValueProviderFactory中就应该含有这里的bindingContext.ModelName;当我们实际参数值在FormValueProviderFactory、QueryStringValueProviderFactory中,如果我们的Action参数是简单数据类型,那么ValueProviderFactory也含有该bindingContext.ModelName,例如我们的Action定义为  public ActionResult Index(string name,string age) 访问url为http://localhost:7503/home/index?name=majiang&age=27,跑的流程主要是和Html.RenderAction调用一样,bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)为true

    好让我们仔细看看

     bool performRequestValidation = ShouldPerformRequestValidation(controllerContext, bindingContext);
                    ValueProviderResult vpResult = bindingContext.UnvalidatedValueProvider.GetValue(bindingContext.ModelName, skipValidation: !performRequestValidation);
                    if (vpResult != null) {
                        return BindSimpleModel(controllerContext, bindingContext, vpResult);
                    }

    这几句 默认情况下performRequestValidation 为true,表示验证结果数据,而ModelBindingContext的UnvalidatedValueProvider

       internal IUnvalidatedValueProvider UnvalidatedValueProvider {
                get {
                    return (ValueProvider as IUnvalidatedValueProvider) ?? new UnvalidatedValueProviderWrapper(ValueProvider);
                }
            }

    这里的bindingContext.UnvalidatedValueProvider.GetValue方法我想就很好明白了,不多说了。正常情况下vpResult 也不为null

     internal object BindSimpleModel(ControllerContext controllerContext, ModelBindingContext bindingContext, ValueProviderResult valueProviderResult) {
                bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
    
                // if the value provider returns an instance of the requested data type, we can just short-circuit
                // the evaluation and return that instance
                if (bindingContext.ModelType.IsInstanceOfType(valueProviderResult.RawValue)) {
                    return valueProviderResult.RawValue;
                }
    
                // since a string is an IEnumerable<char>, we want it to skip the two checks immediately following
                if (bindingContext.ModelType != typeof(string)) {
    
                    // conversion results in 3 cases, as below
                    if (bindingContext.ModelType.IsArray) {
                        // case 1: user asked for an array
                        // ValueProviderResult.ConvertTo() understands array types, so pass in the array type directly
                        object modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);
                        return modelArray;
                    }
    
                    Type enumerableType = TypeHelpers.ExtractGenericInterface(bindingContext.ModelType, typeof(IEnumerable<>));
                    if (enumerableType != null) {
                        // case 2: user asked for a collection rather than an array
                        // need to call ConvertTo() on the array type, then copy the array to the collection
                        object modelCollection = CreateModel(controllerContext, bindingContext, bindingContext.ModelType);
                        Type elementType = enumerableType.GetGenericArguments()[0];
                        Type arrayType = elementType.MakeArrayType();
                        object modelArray = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, arrayType);
    
                        Type collectionType = typeof(ICollection<>).MakeGenericType(elementType);
                        if (collectionType.IsInstanceOfType(modelCollection)) {
                            CollectionHelpers.ReplaceCollection(elementType, modelCollection, modelArray);
                        }
                        return modelCollection;
                    }
                }
    
                // case 3: user asked for an individual element
                object model = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);
                return model;
            }
    

      BindSimpleModel方法相对简单,但是也还是比较复杂,我这里先看第一句

      bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);

       public void SetModelValue(string key, ValueProviderResult value) {
                GetModelStateForKey(key).Value = value;

            }

      private ModelState GetModelStateForKey(string key) {
                if (key == null) {
                    throw new ArgumentNullException("key");
                }
                ModelState modelState;
                if (!TryGetValue(key, out modelState)) {
                    modelState = new ModelState();
                    this[key] = modelState;
                }
                return modelState;
            }
    从这里我们可以看到一个key对应一个ModelState 对应一个ValueProviderResult 

    这个 方法最后一句
      object model = ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName, valueProviderResult, bindingContext.ModelType);方法ConvertProviderResult实际就一句
                   object convertedValue = valueProviderResult.ConvertTo(destinationType);意思就是把数据转换成我们需要的数据类型。

    如果这里的convertedValue 为null,且bindingContext.ModelType为负责类型那么我们就要调用一次BindComplexModel,有前面的分析我们也知道bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)返回false也会调用BindComplexModel方法。

    BindComplexModel方法非常复杂,这里有一句object model = bindingContext.Model; 而bindingContext.Model实际上是 return ModelMetadata.Model;具体的实现如下:

     public object Model {
                get {
                    if (_modelAccessor != null) {
                        _model = _modelAccessor();
                        _modelAccessor = null;
                    }
                    return _model;

                }
                set {
                    _model = value;
                    _modelAccessor = null;
                    _properties = null;
                    _realModelType = null;
                }
            }

    默认 情况下这个_modelAccessor==null的,在ControllerActionInvoker.GetParameterValue方法中bindingContext的 

    ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),参数null造成的,GetMetadataForType的具体实现:

      private IEnumerable<ModelMetadata> GetMetadataForPropertiesImpl(object container, Type containerType) {
                foreach (PropertyDescriptor property in GetTypeDescriptor(containerType).GetProperties()) {
                    Func<object> modelAccessor = container == null ? null : GetPropertyValueAccessor(container, property);
                    yield return GetMetadataForProperty(modelAccessor, containerType, property);
                }
            }

    现在我们又回到DefaultModelBinder的BindComplexModel中来,这里面有一句

      if (model == null) {
                    model = CreateModel(controllerContext, bindingContext, modelType);
                }

    所以一般情况下 BindComplexModel不会返回null值,大家要切记啊

    BindComplexModel会把当前数据类型依次转化为typeof(IDictionary<,>)类型如果成功就按照字典来处理,调用UpdateDictionary方法,如果转化为typeof(IDictionary<,>失败就转化为 typeof(IEnumerable<>)按照集合来处理,调用UpdateCollection方法,如果这种转化也不行的话就按照普通的强类型来处理调用BindComplexElementalModel方法,这种绑定是我们在强类型情况下用的最多的情况。BindComplexElementalModel里面的核心代码是调用

         BindProperties(controllerContext, newBindingContext);方法,

       private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) {
                IEnumerable<PropertyDescriptor> properties = GetFilteredModelProperties(controllerContext, bindingContext);
                foreach (PropertyDescriptor property in properties) {
                    BindProperty(controllerContext, bindingContext, property);
                }
            }

    对 DefaultModelBinder的具体实现很复杂,我们在写Action时应该知道BindModel的时候里面究竟走的是BindSimpleModel还是BindComplexModel,还有参数具体是由哪个ValueProviderDictionary提供的

  • 相关阅读:
    init进程解析rc文件的相关函数分析
    Ubuntu 安装Android Studio与使用手册
    Vim的撤销与重做
    Vim 配色设置与配色脚本语法
    js常用的语句
    xshell常用的命令
    java常用的语句
    maven工程配置日志
    根据一个oss的pdf文件的 地址转换成一个File文件
    根据一个oss的地址把图片转换成一个文件
  • 原文地址:https://www.cnblogs.com/majiang/p/2764456.html
Copyright © 2011-2022 走看看