[转载]深入分析 ASP.NET Mvc 1.0

[转载]深入分析 ASP.NET Mvc 1.0 – ModelBinder – Never give up – 博客园.

前一篇文章已经讲叙Controller.Execute(…)方法的执行流程中会调用 ControllerActionInvoker类的InvokeAction(ControllerContext controllerContext, string actionName)方法, 在InvokeAction(…)方法内又调用了GetParameterValues(…)方法,这个方法为Action中的每个参数赋值,追踪到 GetParameterValues(…)方法内部会发现其实每个参数的值是由GetParameterValue(…)返回的,观察 GetParameterValue(…)方法的源码:

protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) {
            // collect all of the necessary binding properties
            Type parameterType = parameterDescriptor.ParameterType;
            IModelBinder binder = GetModelBinder(parameterDescriptor);
            IDictionary<string, ValueProviderResult> valueProvider = controllerContext.Controller.ValueProvider;
            string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
            Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor);

            // finally, call into the binder
            ModelBindingContext bindingContext = new ModelBindingContext() {
                FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified
                ModelName = parameterName,
                ModelState = controllerContext.Controller.ViewData.ModelState,
                ModelType = parameterType,
                PropertyFilter = propertyFilter,
                ValueProvider = valueProvider
            };
            object result = binder.BindModel(controllerContext, bindingContext);
            return result;
        }

代码的逻辑分为:

  1. 获取继承了IModelBinder接口的对象
  2. 执行IModelBinder对象的BindModel(…)方法并返回Action的参数值

继承IModelBinder接口的对象有三个类:DefaultModelBinder, FormCollectionModelBinderHttpPostedFileBaseModelBinder

在GetParameterValue(…)方法中可以看到由GetModelBinder(parameterDescriptor)负责返回 IModelBinder对象,但他是如何确定返回哪个ModeBinder的呢?

  • 当Action的参数类型为FormCollection时,GetParameterValue(…)方法返回 FormCollectoinModelBinder对象
  • 当Action的参数类型为HttpPostedFileBase时,GetParameterValue(…)方法返回 HttpPostedFileBaseModelBinder对象
  • 当Action的参数类型除了FormCollection和HttpPostedFileBase外(int, string, boolean或强类型),GetParameterValue(…)方法返回DefaultModelBinder对 象,DefaultModelBinder也是最常用的ModelBinder

来深入到GetModelBinder(…)方法的内部

private IModelBinder GetModelBinder(ParameterDescriptor parameterDescriptor) {
            // look on the parameter itself, then look in the global table
            return parameterDescriptor.BindingInfo.Binder ?? Binders.GetBinder(parameterDescriptor.ParameterType);
        }

通常情况下IModelBinder都是由 Binders.GetBinder(parameterDescriptor.ParameterType)返回, Binders属性调用ModelBinders.Binders返回ModelBinderDictionary的静态实例,在调用 ModelBinders.Binders属性返回ModelBinderDictionary对象之前先初始化 ModelBinderDictionary对象并将HttpPostedFileBaseModelBinder保存到 ModelBinderDictionary对象中(代码如下),最后其实是调用ModelBinderDictionary的GetBinder()方 法返回的IModelBinder对象。

public static class ModelBinders {
        private static readonly ModelBinderDictionary _binders = CreateDefaultBinderDictionary();

        public static ModelBinderDictionary Binders {
            get {
                return _binders;
            }
        }
        
        private static ModelBinderDictionary CreateDefaultBinderDictionary() {
            // We can't add a binder to the HttpPostedFileBase type as an attribute, so we'll just
            // prepopulate the dictionary as a convenience to users.

            ModelBinderDictionary binders = new ModelBinderDictionary() {
                { typeof(HttpPostedFileBase), new HttpPostedFileBaseModelBinder() }
            };
            return binders;
        }

    }

ModelBinderDictionary的GetBinder()方法

public IModelBinder GetBinder(Type modelType) {
            return GetBinder(modelType, true /* fallbackToDefault */);
        }

        public virtual IModelBinder GetBinder(Type modelType, bool fallbackToDefault) {
            if (modelType == null) {
                throw new ArgumentNullException("modelType");
            }

            return GetBinder(modelType, (fallbackToDefault) ? DefaultBinder : null);
        }

        private IModelBinder GetBinder(Type modelType, IModelBinder fallbackBinder) {
            // Try to look up a binder for this type. We use this order of precedence:
            // 1. Binder registered in the global table
            // 2. Binder attribute defined on the type
            // 3. Supplied fallback binder

            IModelBinder binder;
            if (_innerDictionary.TryGetValue(modelType, out binder)) {
                return binder;
            }

            binder = ModelBinders.GetBinderFromAttributes(modelType,
                () => String.Format(CultureInfo.CurrentUICulture, MvcResources.ModelBinderDictionary_MultipleAttributes, modelType.FullName));

            return binder ?? fallbackBinder;
        }

在签名为private IModelBinder GetBinder(Type modelType, IModelBinder fallbackBinder) 的方法中会最返回IModelBinder对象,并且可以看出如果Action的参为类型为HttpPostedFileBase时,在 _innerDictionary.TryGetValue(modelType, out binder) 时就可能找到这个IModelBinder并返回;如果参数的类型为FormCollection 时,ModelBinders.GetBinderFromAttributes()会返回FormControllerModelBinder否则会返 回fallbackBinder也主是DefaultModelBinder对象

来看一个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) && !DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, 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() {
                        Model = bindingContext.Model,
                        ModelState = bindingContext.ModelState,
                        ModelType = bindingContext.ModelType,
                        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) {
                ValueProviderResult vpResult;
                bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName, out vpResult);
                if (vpResult != null) {
                    return BindSimpleModel(controllerContext, bindingContext, vpResult);
                }
            }
            if (TypeDescriptor.GetConverter(bindingContext.ModelType).CanConvertFrom(typeof(string))) {
                return null;
            }

            return BindComplexModel(controllerContext, bindingContext);
        }

if (!String.IsNullOrEmpty(bindingContext.ModelName) && !DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, bindingContext.ModelName))  查找指定的参数名是否包含在ValueProvider中,有两种情况:

  1. 在ValueProvider找到指定的参数名: 一般都是简单类型(int, boolean, string)或数组(页面中相同name的元素会以数组的形式赋给指定的参数)
  2. 没有在ValueProvider中找到指定的参数名:通常情况下都是strongly-typed类型,因为他的参数名不能与页面中的name 属性相同,否则会有异常被抛出;还有一种情况就是页面中元素的name属性与Action的参数名不一致,这时如果参数不能接收null类型就会有异常抛 出 。

接着上面的if语句,如果没有找到指定的参数并且 ModelBindingContext.FallbackToEmptyPrefix==true,那么就重新创建一个 ModelBindingContext对象,并设置performedFallback = true。

接下来的代码分两步执行:

  • BindSimpleModel(controllerContext, bindingContext, vpResult): 为简单对象返回参数值
  • BindComplexModel(controllerContext, bindingContext): 返 回complex对象(这里我理解为strongly-typed)的参数值
1. 首先看看BindSimpleModel(…)方法的过程:
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 = 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;
        }

valueProviderResult.RawValue就是简单类型的参数值,if (bindingContext.ModelType.IsInstanceOfType(valueProviderResult.RawValue)) 是判断RawValue是否是指定的参数类型的实例,如果是那就直接返回RawValue。 跳转到最后ConvertProviderResult(…)方法处进行分析,上面的代码都是对数组的操作,最后也还是会调用 ConvertProviderResult(…)方法,来看看ConvertProviderResult(…)方法的代码:

[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
            Justification = "We're recording this exception so that we can act on it later.")]
        private static object ConvertProviderResult(ModelStateDictionary modelState, string modelStateKey, ValueProviderResult valueProviderResult, Type destinationType) {
            try {
                object convertedValue = valueProviderResult.ConvertTo(destinationType);
                return convertedValue;
            }
            catch (Exception ex) {
                modelState.AddModelError(modelStateKey, ex);
                return null;
            }
        }

调用ValueProviderResult类(ValueProviderResult.cs)的ConvertTo方法, 后又跳转到UnwrapPossibleArrayType(…)方法

        public object ConvertTo(Type type) {
            return ConvertTo(type, null /* culture */);
        }

        public virtual object ConvertTo(Type type, CultureInfo culture) {
            if (type == null) {
                throw new ArgumentNullException("type");
            }

            CultureInfo cultureToUse = culture ?? Culture;
            return UnwrapPossibleArrayType(cultureToUse, RawValue, type);
        }

        private static object UnwrapPossibleArrayType(CultureInfo culture, object value, Type destinationType) {
            if (value == null || destinationType.IsInstanceOfType(value)) {
                return value;
            }

            // array conversion results in four cases, as below
            Array valueAsArray = value as Array;
            if (destinationType.IsArray) {
                Type destinationElementType = destinationType.GetElementType();
                if (valueAsArray != null) {
                    // case 1: both destination + source type are arrays, so convert each element
                    IList converted = Array.CreateInstance(destinationElementType, valueAsArray.Length);
                    for (int i = 0; i < valueAsArray.Length; i++) {
                        converted[i] = ConvertSimpleType(culture, valueAsArray.GetValue(i), destinationElementType);
                    }
                    return converted;
                }
                else {
                    // case 2: destination type is array but source is single element, so wrap element in array + convert
                    object element = ConvertSimpleType(culture, value, destinationElementType);
                    IList converted = Array.CreateInstance(destinationElementType, 1);
                    converted[0] = element;
                    return converted;
                }
            }
            else if (valueAsArray != null) {
                // case 3: destination type is single element but source is array, so extract first element + convert
                if (valueAsArray.Length > 0) {
                    value = valueAsArray.GetValue(0);
                    return ConvertSimpleType(culture, value, destinationType);
                }
                else {
                    // case 3(a): source is empty array, so can't perform conversion
                    return null;
                }
            }
            // case 4: both destination + source type are single elements, so convert
            return ConvertSimpleType(culture, value, destinationType);
        }

在UnwrapPossibleArrayType(…)方法中首先是一些对数组的判断和操作,如果参数的类型不是Array那么就运行最后一行的 ConvertSimpleType(…)方法并返回参数值

private static object ConvertSimpleType(CultureInfo culture, object value, Type destinationType) {
            if (value == null || destinationType.IsInstanceOfType(value)) {
                return value;
            }

            // if this is a user-input value but the user didn't type anything, return no value
            string valueAsString = value as string;
            if (valueAsString != null && valueAsString.Length == 0) {
                return null;
            }

            TypeConverter converter = TypeDescriptor.GetConverter(destinationType);
            bool canConvertFrom = converter.CanConvertFrom(value.GetType());
            if (!canConvertFrom) {
                converter = TypeDescriptor.GetConverter(value.GetType());
            }
            if (!(canConvertFrom || converter.CanConvertTo(destinationType))) {
                string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ValueProviderResult_NoConverterExists,
                    value.GetType().FullName, destinationType.FullName);
                throw new InvalidOperationException(message);
            }

            try {
                object convertedValue = (canConvertFrom) ?
                     converter.ConvertFrom(null /* context */, culture, value) :
                     converter.ConvertTo(null /* context */, culture, value, destinationType);
                return convertedValue;
            }
            catch (Exception ex) {
                string message = String.Format(CultureInfo.CurrentUICulture, MvcResources.ValueProviderResult_ConversionThrew,
                    value.GetType().FullName, destinationType.FullName);
                throw new InvalidOperationException(message, ex);
            }
        }

TypeConverter converter = TypeDescriptor.GetConverter(destinationType) 来返回一个destinationType类型TypeConverter转换器,并用convert.CanConvertFrom和 CanConvertTo两个方法是否可以从value.GetType()转换为destationType类型,如果可以转换那么就将转换的返回给 Action对应的参数,否则换出一个异常。

2. BindComplexModel

在BindComplexModel中只讨论strongly-typed。先来看看BindComplexModel(…)方法的代码

internal object BindComplexModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
            object model = bindingContext.Model;
            Type modelType = bindingContext.ModelType;
            
            // if we're being asked to create an array, create a list instead, then coerce to an array after the list is created
            if (model == null && modelType.IsArray) {
                Type elementType = modelType.GetElementType();
                Type listType = typeof(List<>).MakeGenericType(elementType);
                object collection = CreateModel(controllerContext, bindingContext, listType);

                ModelBindingContext arrayBindingContext = new ModelBindingContext() {
                    Model = collection,
                    ModelName = bindingContext.ModelName,
                    ModelState = bindingContext.ModelState,
                    ModelType = listType,
                    PropertyFilter = bindingContext.PropertyFilter,
                    ValueProvider = bindingContext.ValueProvider
                };
                IList list = (IList)UpdateCollection(controllerContext, arrayBindingContext, elementType);

                if (list == null) {
                    return null;
                }

                Array array = Array.CreateInstance(elementType, list.Count);
                list.CopyTo(array, 0);
                return array;
            }

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

            // special-case IDictionary<,> and ICollection<>
            Type dictionaryType = ExtractGenericInterface(modelType, typeof(IDictionary<,>));
            if (dictionaryType != null) {
                Type[] genericArguments = dictionaryType.GetGenericArguments();
                Type keyType = genericArguments[0];
                Type valueType = genericArguments[1];

                ModelBindingContext dictionaryBindingContext = new ModelBindingContext() {
                    Model = model,
                    ModelName = bindingContext.ModelName,
                    ModelState = bindingContext.ModelState,
                    ModelType = modelType,
                    PropertyFilter = bindingContext.PropertyFilter,
                    ValueProvider = bindingContext.ValueProvider
                };
                object dictionary = UpdateDictionary(controllerContext, dictionaryBindingContext, keyType, valueType);
                return dictionary;
            }

            Type enumerableType = ExtractGenericInterface(modelType, typeof(IEnumerable<>));
            if (enumerableType != null) {
                Type elementType = enumerableType.GetGenericArguments()[0];

                Type collectionType = typeof(ICollection<>).MakeGenericType(elementType);
                if (collectionType.IsInstanceOfType(model)) {
                    ModelBindingContext collectionBindingContext = new ModelBindingContext() {
                        Model = model,
                        ModelName = bindingContext.ModelName,
                        ModelState = bindingContext.ModelState,
                        ModelType = modelType,
                        PropertyFilter = bindingContext.PropertyFilter,
                        ValueProvider = bindingContext.ValueProvider
                    };
                    object collection = UpdateCollection(controllerContext, collectionBindingContext, elementType);
                    return collection;
                }
            }

            // otherwise, just update the properties on the complex type
            BindComplexElementalModel(controllerContext, bindingContext, model);
            return model;
        }

先从ModelBindingContext中 获取model和modelType,在BindComplexModel方法的中间判断model是否为null,如果不是将调用 CreateModel(…)方法来创建一个strongly-typed对象的实例: if (model == null) { model = CreateModel(controllerContext,bindingContext,modelType); } 。跳转到倒数第二行的BindComplexElementalModel(…)方法处

internal void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) {
            // need to replace the property filter + model object and create an inner binding context
            BindAttribute bindAttr = (BindAttribute)TypeDescriptor.GetAttributes(bindingContext.ModelType)[typeof(BindAttribute)];
            Predicate<string> newPropertyFilter = (bindAttr != null)
                ? propertyName => bindAttr.IsPropertyAllowed(propertyName) && bindingContext.PropertyFilter(propertyName)
                : bindingContext.PropertyFilter;

            ModelBindingContext newBindingContext = new ModelBindingContext() {
                Model = model,
                ModelName = bindingContext.ModelName,
                ModelState = bindingContext.ModelState,
                ModelType = bindingContext.ModelType,
                PropertyFilter = newPropertyFilter,
                ValueProvider = bindingContext.ValueProvider
            };

            // validation
            if (OnModelUpdating(controllerContext, newBindingContext)) {
                BindProperties(controllerContext, newBindingContext);
                OnModelUpdated(controllerContext, newBindingContext);
            }
        }

先获取参数前面的BindAttribute,创建一个新的ModelBindingContext对 象并重新设置了PropertyFilter属性,在下面的if语句中调用了三个方法,OnModelUpdating(…)永远返回true,接着是 BindProperties(…),这个方法为strongly-typed对象的每个属性赋值,来看看它的源码:

private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) {
            PropertyDescriptorCollection properties = GetModelProperties(controllerContext, bindingContext);
            foreach (PropertyDescriptor property in properties) {
                BindProperty(controllerContext, bindingContext, property);
            }
        }

调用GetModelProperties(…)方法返回一个PropertyDescriptorCollection 集合对象,注意,这个集合不一定是stronly-typed中的全部属性,在GetModelProperties()方法中会根据参 数前面定义的BindAttribute.Incude和BindAttribute.Exclude过滤掉不被使用的属性,将那些确定使用的属性存放到PropertyDescriptorCollection 中。 在foreach中调用BindProperty(…)为每个可用的属性赋值,

protected virtual void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) {
            // need to skip properties that aren't part of the request, else we might hit a StackOverflowException
            string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
            if (!DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, fullPropertyKey)) {
                return;
            }

            // call into the property's model binder
            IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);
            object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model);
            ModelBindingContext innerBindingContext = new ModelBindingContext() {
                Model = originalPropertyValue,
                ModelName = fullPropertyKey,
                ModelState = bindingContext.ModelState,
                ModelType = propertyDescriptor.PropertyType,
                ValueProvider = bindingContext.ValueProvider
            };
            object newPropertyValue = propertyBinder.BindModel(controllerContext, innerBindingContext);

            // validation
            if (OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, newPropertyValue)) {
                SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
                OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
            }
        }

在BindProperty(…)内部递归调用IModelBinder.GetBinder(…)方法来为属性赋值。

ModelBinder并不复杂,Action所有的参数值都是循环调用IModelBinder.GetBinder()方法获得,而 complex类型递归调用IModelBinder来为complex类型的可用属性赋值。

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏