700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > asp.net mvc源码分析-Action篇 DefaultModelBinder

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

时间:2021-07-04 00:42:54

相关推荐

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

接着上篇 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 instanceif (bindingContext.ModelType.IsInstanceOfType(valueProviderResult.RawValue)) {return valueProviderResult.RawValue;}// since a string is an IEnumerable<char>, we want it to skip the two checks immediately followingif (bindingContext.ModelType != typeof(string)) {// conversion results in 3 cases, as belowif (bindingContext.ModelType.IsArray) {// case 1: user asked for an array// ValueProviderResult.ConvertTo() understands array types, so pass in the array type directlyobject 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 collectionobject 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 elementobject 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提供的。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。