上回书说到ModelBinderDictionary类的GetBinder方法的实现原理,今天继续解释第四条注释。
4、用户提供的备用Binder
这一条很简单,fallbackBinder是用户提供的一个备用Binder,从语句【returnbinder ?? fallbackBinder;】可以看出,如果第三条也没有得到合适的IModelBinder的话,则返回由用户提供的备用fallbackBinder。
上篇提到理解模型绑定需要搞清的两个要点:一是MVC如何得到IModelBinder接口的实例,二是该接口的BindModel方法如何把Request相关信息绑定到相应Model之上。第一点在上篇中已经解释过,下面我们看第二点,即:该接口的BindModel方法如何把Request相关信息绑定到相应Model之上。
在ControllerActionInvoker类的GetParameterValue方法中找到代码:【objectresult = binder.BindModel(controllerContext, bindingContext);】,这句是执行模型绑定的实际代码。把鼠标放在BindModel方法上,按F12进入其定义(代码段1)。
public interface IModelBinder { object BindModel(ControllerContextcontrollerContext, ModelBindingContext bindingContext); }
代码段 1
我们发现BindModel方法是接口IModelBinder的一个方法,当然,接口IModelBinder非常简单,只有一个方法。所以我们猜测肯定有某些类实现了IModelBinder接口,通过在源码中查找,找到了该接口的实现类有:ByteArrayModelBinder、CancellationTokenModelBinder、DefaultModelBinder、FormCollectionModelBinder、HttpPostedFileBaseModelBinder、DeserializingModelBinder、ExtensibleModelBinderAdapter、ResourceModelBinder以及NullBinder。是不是有蒙圈的感觉?好吧,我也蒙了,这多一大堆。
我们只看较为常用的DefaultModelBinder中关于BindModel方法的实现,见代码段2。这个方法比较长,我们只挑重点的部分看。其实该方法总体上是把需要绑定的模型分为简单模型和复杂模型,分别由BindSimpleModel和BindComplexModel两个方法负责具体实现。根据注释可知,MVC把诸如int、string等简单数据类型作为简单模型,其它类作为复杂类型。
public virtual objectBindModel(ControllerContext controllerContext, ModelBindingContextbindingContext) { RuntimeHelpers.EnsureSufficientExecutionStack(); 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 withthe prefix. If this is the top-level element, fall back // to the empty prefix. if(bindingContext.FallbackToEmptyPrefix) { bindingContext = newModelBindingContext() { 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 therequest exactly matches the name of the model we're binding. // Complex type = everything else. if (!performedFallback) { bool performRequestValidation =ShouldPerformRequestValidation(controllerContext, bindingContext); ValueProviderResultvalueProviderResult = bindingContext.UnvalidatedValueProvider.GetValue(bindingContext.ModelName,skipValidation: !performRequestValidation); if (valueProviderResult !=null) { returnBindSimpleModel(controllerContext, bindingContext, valueProviderResult); } } if(!bindingContext.ModelMetadata.IsComplexType) { return null; } returnBindComplexModel(controllerContext, bindingContext); }
代码段 2
我们分别来看BindSimpleModel方法和BindComplexModel方法。
代码段3为BindSimpleModel方法的定义,该方法的条理还是比较清晰的,注释也比较完备。大概执行过程是:如果valueprovider返回的是请求的数据类型,则不再执行接下来的代码,而直接返回该valueprovider的值;否则检查请求的类型,分别按照数组、集合和普通类的次序进行处理。
internal objectBindSimpleModel(ControllerContext controllerContext, ModelBindingContextbindingContext, ValueProviderResult valueProviderResult) { bindingContext.ModelState.SetModelValue(bindingContext.ModelName,valueProviderResult); // if the value provider returns aninstance of the requested data type, we can just short-circuit // the evaluation and return thatinstance if(bindingContext.ModelType.IsInstanceOfType(valueProviderResult.RawValue)) { return valueProviderResult.RawValue; } // since a string is anIEnumerable<char>, we want it to skip the two checks immediatelyfollowing if (bindingContext.ModelType !=typeof(string)) { // conversion results in 3 cases,as below if(bindingContext.ModelType.IsArray) { // case 1: user asked foran array //ValueProviderResult.ConvertTo() understands array types, so pass in the arraytype 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 acollection 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 anindividual element object model =ConvertProviderResult(bindingContext.ModelState, bindingContext.ModelName,valueProviderResult, bindingContext.ModelType); return model; }
代码段 3
未完待续。。。