深入ASP.NET MVC之六:Model Binding的實現


Filter和Action的執行 中說到,ControllerActionInvoker對象在InvokeAction方法中調用了GetParameters方法實現了model binding,先來看下這個方法:

protected virtual IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor) {
            Dictionary<string, object> parametersDict = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
            ParameterDescriptor[] parameterDescriptors = actionDescriptor.GetParameters();

            foreach (ParameterDescriptor parameterDescriptor in parameterDescriptors) {
                parametersDict[parameterDescriptor.ParameterName] = GetParameterValue(controllerContext, parameterDescriptor);
            }
            return parametersDict;
        }

首先通過actionDescriptor獲得action參數的信息,這里的actionDescriptor實際上是一個ReflectedActionDescriptor,獲得參數的方法自然是通過反射,不深入分析了。緊接着就對每個參賽調用了GetParameterValue方法:

        protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) {
            // collect all of the necessary binding properties
            Type parameterType = parameterDescriptor.ParameterType;
            IModelBinder binder = GetModelBinder(parameterDescriptor);
            IValueProvider 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
                ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
                ModelName = parameterName,
                ModelState = controllerContext.Controller.ViewData.ModelState,
                PropertyFilter = propertyFilter,
                ValueProvider = valueProvider
            };
            object result = binder.BindModel(controllerContext, bindingContext);
            return result ?? parameterDescriptor.DefaultValue;
        }

這個方法中完成了幾件事,首先是獲得Model Binder,其次是獲得Value Provider,再獲得Property Filter,最終把這些信息組成ModelBindingContext,交給binder的BindModel方法實現綁定。先看如何獲得Model Binder和Value Provider,這里將是擴展model binding的着手點。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);
        }

如果通過action的參數的Atrribute說明了采用什么binder的話就優先使用這個binder,否則從全局binder中查找。這里Binders是一個ModelBinderDictionary對象,其初始化是在ModelBinders類中的CreateDefaultBinderDictionary方法:

        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() },
                { typeof(byte[]), new ByteArrayModelBinder() },
                { typeof(Binary), new LinqBinaryModelBinder() }
            };
            return binders;
        }

在這里准備了幾個默認的ModelBinder.准備工作完成之后,看真正的GetBinder方法,這個方法經過幾個重載的方法之后,最終調用的是如下的方法:

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

這里fallbackToDefault是true,DefaultBinder就是一個DefaultModelBinder對象,這個方法的實現如下:

        private IModelBinder GetBinder(Type modelType, IModelBinder fallbackBinder) {
            // Try to look up a binder for this type. We use this order of precedence:
            // 1. Binder returned from provider
            // 2. Binder registered in the global table
            // 3. Binder attribute defined on the type
            // 4. Supplied fallback binder
            IModelBinder binder = _modelBinderProviders.GetBinder(modelType);
            if (binder != null) {
                return binder;
            }
            if (_innerDictionary.TryGetValue(modelType, out binder)) {
                return binder;
            }
            binder = ModelBinders.GetBinderFromAttributes(modelType,
                () => String.Format(CultureInfo.CurrentCulture, MvcResources.ModelBinderDictionary_MultipleAttributes, modelType.FullName));
            return binder ?? fallbackBinder;
        }

 

加上注釋的幫助,很容易理解獲取一個類型對應的model binder的過程是如何的。首先是通過IModelBinderProvider來查找,這里的modelBinderProviders在默認情況下就是ModelBinderProviders.BinderProviders屬性,這是ModelBinderProviderCollection類型的對象。我們要使用自定義的model binder,一個方法就是實現一個IModelBinderProivder,並且通過ModelBinderProviders.BinderProviders.Add方法注冊到全局的provider表中。第二個途徑是通過現有的binder表,這里的_innerDictionary是一個Dictionary<Type, IModelBinder>類型的對象,也就是ModelBinderDictionary實際存儲數據的地方,ModelBinders.Binders.Add方法就會直接往這個dictionary中添加數據。第三個途徑是通過待綁定類型的Attribute來加載binder。最后,如果上面的途徑都沒有找到binder,那么就用默認的DefaultModelBinder,大多數時候這個DefaultModelBinder已經足夠強大。暫時先跳過這個DefaultModelBinder的實現,再回到GetParameterValue中,當得到合適的model binder之后還需要獲得value provider:

IValueProvider valueProvider = controllerContext.Controller.ValueProvider;

這里的ValueProvider是定義在ControllerBase類型中的:

        public IValueProvider ValueProvider {
            get {
                if (_valueProvider == null) {
                    _valueProvider = ValueProviderFactories.Factories.GetValueProvider(ControllerContext);
                }
                return _valueProvider;
            }
            set {
                _valueProvider = value;
            }
        }

下面來看下 ValueProviderFactories的實現:

    public static class ValueProviderFactories {

        private static readonly ValueProviderFactoryCollection _factories = new ValueProviderFactoryCollection() {
            new ChildActionValueProviderFactory(),
            new FormValueProviderFactory(),
            new JsonValueProviderFactory(),
            new RouteDataValueProviderFactory(),
            new QueryStringValueProviderFactory(),
            new HttpFileCollectionValueProviderFactory(),
        };

        public static ValueProviderFactoryCollection Factories {
            get {
                return _factories;
            }
        }
    }

這個類初始化了一系列默認的ValueProviderFactory。再看下GetValueProvider(ControllerContext) 這個方法的實現:

        public IValueProvider GetValueProvider(ControllerContext controllerContext) {
            var valueProviders = from factory in _serviceResolver.Current
                                 let valueProvider = factory.GetValueProvider(controllerContext)
                                 where valueProvider != null
                                 select valueProvider;

            return new ValueProviderCollection(valueProviders.ToList());
        }

這里的做法是返回所有的能夠找到的value provider,再將其組合成一個ValueProviderCollection.再看下ValueProviderCollection的關鍵方法的實現:

        public virtual ValueProviderResult GetValue(string key) {
            return GetValue(key, skipValidation: false);
        }

        public virtual ValueProviderResult GetValue(string key, bool skipValidation) {
            return (from provider in this
                    let result = GetValueFromProvider(provider, key, skipValidation)
                    where result != null
                    select result).FirstOrDefault();
        }

        internal static ValueProviderResult GetValueFromProvider(IValueProvider provider, string key, bool skipValidation) {
            // Since IUnvalidatedValueProvider is a superset of IValueProvider, it's always OK to use the
            // IUnvalidatedValueProvider-supplied members if they're present. Otherwise just call the
            // normal IValueProvider members.

            IUnvalidatedValueProvider unvalidatedProvider = provider as IUnvalidatedValueProvider;
            return (unvalidatedProvider != null) ? unvalidatedProvider.GetValue(key, skipValidation) : provider.GetValue(key);
        }

這個方法返回的是第一個能夠找到value的ValueProvider返回的值。附上函數調用圖:

modelbind


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM