您的位置:首页 > 编程语言 > ASP

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

2012-11-10 20:36 811 查看
接着上篇 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提供的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: