asp.net mvc原始碼分析-Action篇 DefaultModelBinder

weixin_34377065發表於2012-11-10

接著上篇 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提供的

相關文章