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