asp.net mvc源碼分析-Action篇 DefaultModelBinder

來源:互聯網
上載者:User

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.