原文:asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证

在前面的文章中我们曾经涉及到ControllerActionInvoker类GetParameterValue方法中有这么一句代码:

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

            };

这里的PropertyFilter属性表示在绑定的时候参数是否需要绑定数据,为true表示绑定数据,ValueProvider 属性表示什么就很明白,ModelName 为绑定信息的Prefix属性或则是我们的参数名。同时我们还知道ModelMetadataProviders.Current默认就是DataAnnotationsModelMetadataProvider。而DataAnnotationsModelMetadataProvider的GetMetadataForType方法具体实现是在
其父类AssociatedMetadataProvider中实现的:

public override ModelMetadata GetMetadataForType(Func<object> modelAccessor, Type modelType) {

            if (modelType == null) {

                throw new ArgumentNullException("modelType");

            }



            IEnumerable<Attribute> attributes = GetTypeDescriptor(modelType).GetAttributes().Cast<Attribute>();

            ModelMetadata result = CreateMetadata(attributes, null /* containerType */, modelAccessor, modelType, null /* propertyName */);

            ApplyMetadataAwareAttributes(attributes, result);

            return result;

        }

IEnumerable<Attribute> attributes = GetTypeDescriptor(modelType).GetAttributes().Cast<Attribute>();这句查找当前modelType的所有特性(这里的modelType主要是自定义的那些强类型,如果是内置类型就没有意义了)。

ModelMetadata result = CreateMetadata(attributes, null /* containerType */, modelAccessor, modelType, null /* propertyName */);这句是真正创建ModelMetadata 的地方,在DataAnnotationsModelMetadataProvider类中从写了。

    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) {
List<Attribute> attributeList = new List<Attribute>(attributes);
DisplayColumnAttribute displayColumnAttribute = attributeList.OfType<DisplayColumnAttribute>().FirstOrDefault();
DataAnnotationsModelMetadata result = new DataAnnotationsModelMetadata(this, containerType, modelAccessor, modelType, propertyName, displayColumnAttribute); // Do [HiddenInput] before [UIHint], so you can override the template hint
HiddenInputAttribute hiddenInputAttribute = attributeList.OfType<HiddenInputAttribute>().FirstOrDefault();
if (hiddenInputAttribute != null) {
result.TemplateHint = "HiddenInput";
result.HideSurroundingHtml = !hiddenInputAttribute.DisplayValue;
} // We prefer [UIHint("...", PresentationLayer = "MVC")] but will fall back to [UIHint("...")]
IEnumerable<UIHintAttribute> uiHintAttributes = attributeList.OfType<UIHintAttribute>();
UIHintAttribute uiHintAttribute = uiHintAttributes.FirstOrDefault(a => String.Equals(a.PresentationLayer, "MVC", StringComparison.OrdinalIgnoreCase))
?? uiHintAttributes.FirstOrDefault(a => String.IsNullOrEmpty(a.PresentationLayer));
if (uiHintAttribute != null) {
result.TemplateHint = uiHintAttribute.UIHint;
} DataTypeAttribute dataTypeAttribute = attributeList.OfType<DataTypeAttribute>().FirstOrDefault();
if (dataTypeAttribute != null) {
result.DataTypeName = dataTypeAttribute.ToDataTypeName();
} EditableAttribute editable = attributes.OfType<EditableAttribute>().FirstOrDefault();
if (editable != null) {
result.IsReadOnly = !editable.AllowEdit;
}
else {
ReadOnlyAttribute readOnlyAttribute = attributeList.OfType<ReadOnlyAttribute>().FirstOrDefault();
if (readOnlyAttribute != null) {
result.IsReadOnly = readOnlyAttribute.IsReadOnly;
}
} DisplayFormatAttribute displayFormatAttribute = attributeList.OfType<DisplayFormatAttribute>().FirstOrDefault();
if (displayFormatAttribute == null && dataTypeAttribute != null) {
displayFormatAttribute = dataTypeAttribute.DisplayFormat;
}
if (displayFormatAttribute != null) {
result.NullDisplayText = displayFormatAttribute.NullDisplayText;
result.DisplayFormatString = displayFormatAttribute.DataFormatString;
result.ConvertEmptyStringToNull = displayFormatAttribute.ConvertEmptyStringToNull; if (displayFormatAttribute.ApplyFormatInEditMode) {
result.EditFormatString = displayFormatAttribute.DataFormatString;
} if (!displayFormatAttribute.HtmlEncode && String.IsNullOrWhiteSpace(result.DataTypeName)) {
result.DataTypeName = DataTypeUtil.HtmlTypeName;
}
} ScaffoldColumnAttribute scaffoldColumnAttribute = attributeList.OfType<ScaffoldColumnAttribute>().FirstOrDefault();
if (scaffoldColumnAttribute != null) {
result.ShowForDisplay = result.ShowForEdit = scaffoldColumnAttribute.Scaffold;
} DisplayAttribute display = attributes.OfType<DisplayAttribute>().FirstOrDefault();
string name = null;
if (display != null) {
result.Description = display.GetDescription();
result.ShortDisplayName = display.GetShortName();
result.Watermark = display.GetPrompt();
result.Order = display.GetOrder() ?? ModelMetadata.DefaultOrder; name = display.GetName();
} if (name != null) {
result.DisplayName = name;
}
else {
DisplayNameAttribute displayNameAttribute = attributeList.OfType<DisplayNameAttribute>().FirstOrDefault();
if (displayNameAttribute != null) {
result.DisplayName = displayNameAttribute.DisplayName;
}
} RequiredAttribute requiredAttribute = attributeList.OfType<RequiredAttribute>().FirstOrDefault();
if (requiredAttribute != null) {
result.IsRequired = true;
} return result;
}

这里的具体创建就不说了,创建了一个DataAnnotationsModelMetadata实例,其中 containerType,propertyName默认是null,modelAccessor是返回null的一个方法,propertyName就是参数名。

ApplyMetadataAwareAttributes(attributes, result);这个方法就是给result设置相应的属性,具体的实现是通过调用IMetadataAware实例的OnMetadataCreated方法。默认有AdditionalMetadataAttribute、AllowHtmlAttribute实现了IMetadataAware接口。

controllerContext.Controller.ViewData.ModelState默认返回的是一个ModelStateDictionary(private readonly ModelStateDictionary _modelState),

默认情况下ModelState里面是没有任何元素的。

由前面的文章我们知道,默认的强类型参数如:

[HttpPost]

        public ActionResult Index(UserInfo Info)

        {

            return View(Info);

        }

这个Info参数的绑定都是走的BindComplexModel->BindComplexElementalModel方法。

internal void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) {

            // need to replace the property filter + model object and create an inner binding context

            ModelBindingContext newBindingContext = CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model);

            // validation

            if (OnModelUpdating(controllerContext, newBindingContext)) {

                BindProperties(controllerContext, newBindingContext);

                OnModelUpdated(controllerContext, newBindingContext);

            }

        }

ModelBindingContext newBindingContext = CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model);这句是创建新的ModelBindingContext,和现有的ModelBindingContext有何不同了,其中ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(()
=> model, bindingContext.ModelType),这里的() => mode究竟有什么影响了,在这个东东对应modelAccessor参数,在ModelMetadata中有一个Model属性。

public object Model {

            get {

                if (_modelAccessor != null) {

                    _model = _modelAccessor();

                    _modelAccessor = null;

                }

                return _model;

            }

            set {

                _model = value;

                _modelAccessor = null;

                _properties = null;

                _realModelType = null;

            }

        }

同时新的ModelBindingContext的PropertyFilter有所改变,

Predicate<string> newPropertyFilter = (bindAttr != null)

                ? propertyName => bindAttr.IsPropertyAllowed(propertyName) && bindingContext.PropertyFilter(propertyName)

                : bindingContext.PropertyFilter;

现在新的ModelBindingContext已经创建。

现在剩下的

if (OnModelUpdating(controllerContext, newBindingContext)) {

                BindProperties(controllerContext, newBindingContext);

                OnModelUpdated(controllerContext, newBindingContext);

            }

这几句的意思也很好明白,   BindProperties(controllerContext, newBindingContext)这是真正绑定数据的地方,绑定数据前后都可以调用相应的方法一个做预处理,一个做后置处理。默认OnModelUpdating直接返回true。BindProperties处理就比较复杂了。

private void BindProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) {

            IEnumerable<PropertyDescriptor> properties = GetFilteredModelProperties(controllerContext, bindingContext);

            foreach (PropertyDescriptor property in properties) {

                BindProperty(controllerContext, bindingContext, property);

            }

        }

首先需要获取那些属性需要绑定,然后在循环一次绑定每个属性。

其中GetFilteredModelProperties的实现如下:

protected IEnumerable<PropertyDescriptor> GetFilteredModelProperties(ControllerContext controllerContext, ModelBindingContext bindingContext) {

            PropertyDescriptorCollection properties = GetModelProperties(controllerContext, bindingContext);

            Predicate<string> propertyFilter = bindingContext.PropertyFilter;



            return from PropertyDescriptor property in properties

                   where ShouldUpdateProperty(property, propertyFilter)

                   select property;

        }

首先获取类型的所有属性描述集合PropertyDescriptorCollection,然后一次过滤调我们不需要绑定的属性。过滤条件的实现是ShouldUpdateProperty方法中。

private static bool ShouldUpdateProperty(PropertyDescriptor property, Predicate<string> propertyFilter) {

            if (property.IsReadOnly && !CanUpdateReadonlyTypedReference(property.PropertyType)) {

                return false;

            }



            // if this property is rejected by the filter, move on

            if (!propertyFilter(property.Name)) {

                return false;

            }



            // otherwise, allow

            return true;

        }

CanUpdateReadonlyTypedReference这个方法很简单,通过property.PropertyType是值类型、数组、string就返回true。BindProperty的实现就比较复杂了。

  protected virtual void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) {
// need to skip properties that aren't part of the request, else we might hit a StackOverflowException
string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
if (!bindingContext.ValueProvider.ContainsPrefix(fullPropertyKey)) {
return;
} // call into the property's model binder
IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);
object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model);
ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
propertyMetadata.Model = originalPropertyValue;
ModelBindingContext innerBindingContext = new ModelBindingContext() {
ModelMetadata = propertyMetadata,
ModelName = fullPropertyKey,
ModelState = bindingContext.ModelState,
ValueProvider = bindingContext.ValueProvider
};
object newPropertyValue = GetPropertyValue(controllerContext, innerBindingContext, propertyDescriptor, propertyBinder);
propertyMetadata.Model = newPropertyValue; // validation
ModelState modelState = bindingContext.ModelState[fullPropertyKey];
if (modelState == null || modelState.Errors.Count == 0) {
if (OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, newPropertyValue)) {
SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
}
}
else {
SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue); // Convert FormatExceptions (type conversion failures) into InvalidValue messages
foreach (ModelError error in modelState.Errors.Where(err => String.IsNullOrEmpty(err.ErrorMessage) && err.Exception != null).ToList()) {
for (Exception exception = error.Exception; exception != null; exception = exception.InnerException) {
if (exception is FormatException) {
string displayName = propertyMetadata.GetDisplayName();
string errorMessageTemplate = GetValueInvalidResource(controllerContext);
string errorMessage = String.Format(CultureInfo.CurrentCulture, errorMessageTemplate, modelState.Value.AttemptedValue, displayName);
modelState.Errors.Remove(error);
modelState.Errors.Add(errorMessage);
break;
}
}
}
}
}

我们首先看看ModelBindingContext的PropertyMetadata属性是什么东东吧。

public IDictionary<string, ModelMetadata> PropertyMetadata {

            get {

                if (_propertyMetadata == null) {

                    _propertyMetadata = ModelMetadata.Properties.ToDictionary(m => m.PropertyName, StringComparer.OrdinalIgnoreCase);

                }



                return _propertyMetadata;

            }

        }

而ModelMetadata的Properties属性定义如下:

public virtual IEnumerable<ModelMetadata> Properties {

            get {

                if (_properties == null) {

                    _properties = Provider.GetMetadataForProperties(Model, RealModelType).OrderBy(m => m.Order);

                }

                return _properties;

            }

        }

GetMetadataForProperties的实现是在AssociatedMetadataProvider类中实现的,循环RealModelType类型的每个属性,每个属性都会创建一个ModelMetadata,创建ModelMetadata的方法还是调用CreateMetadata实现的。所以我们知道ModelBindingContext的PropertyMetadata属性是一个字典集合,key就是属性名,value为一个ModelMetadata。

现在回到BindProperty方法中,它主要是获取属性绑定名称,通过属性类型获取IModelBinder实例,同过bindingContext获取属性对应的ModelMetadata实例,进而创建新的ModelBindingContext实例,从而调用新的IModelBinder实例BindModel方法,获取属性对应的值,最后设置属性对应的值。设置属性对应的值是用过SetProperty方法来实现的。这个方法的代码有点多,实际上很多都不执行的。

现在属性都绑定完了,让我们回到BindComplexElementalModel方法中来,该调用OnModelUpdated方法了:

protected virtual void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) {
Dictionary<string, bool> startedValid = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase); foreach (ModelValidationResult validationResult in ModelValidator.GetModelValidator(bindingContext.ModelMetadata, controllerContext).Validate(null)) {
string subPropertyName = CreateSubPropertyName(bindingContext.ModelName, validationResult.MemberName); if (!startedValid.ContainsKey(subPropertyName)) {
startedValid[subPropertyName] = bindingContext.ModelState.IsValidField(subPropertyName);
} if (startedValid[subPropertyName]) {
bindingContext.ModelState.AddModelError(subPropertyName, validationResult.Message);
}
}
}

这个方法意思很简单,验证数据的有效性。我们先看ModelStateDictionary的IsValidField方法是如何实现的:

return DictionaryHelpers.FindKeysWithPrefix(this, key).All(entry => entry.Value.Errors.Count == 0);是否有错误信息,有表示没有通过验证,没有通过的验证记录相应的验证信息。ModelStateDictionary的AddModelError方法:

public void AddModelError(string key, string errorMessage) {

            GetModelStateForKey(key).Errors.Add(errorMessage);

        }

我们知道每一个key对应一个ModelState,这个方法就是把错误信息写到ModelState对应的Errors属性里面。

下面我们来看看究竟是如何验证数据的。

首先ModelValidator.GetModelValidator方法返回的是一个CompositeModelValidator实例,实际上的验证是调用的CompositeModelValidator的Validate方法:

  public override IEnumerable<ModelValidationResult> Validate(object container) {
bool propertiesValid = true; foreach (ModelMetadata propertyMetadata in Metadata.Properties) {
foreach (ModelValidator propertyValidator in propertyMetadata.GetValidators(ControllerContext)) {
foreach (ModelValidationResult propertyResult in propertyValidator.Validate(Metadata.Model)) {
propertiesValid = false;
yield return new ModelValidationResult {
MemberName = DefaultModelBinder.CreateSubPropertyName(propertyMetadata.PropertyName, propertyResult.MemberName),
Message = propertyResult.Message
};
}
}
} if (propertiesValid) {
foreach (ModelValidator typeValidator in Metadata.GetValidators(ControllerContext)) {
foreach (ModelValidationResult typeResult in typeValidator.Validate(container)) {
yield return typeResult;
}
}
}
}

整个验证分类2部分一部分验证属性,一部分验证类型,先验证属性,如果属性验证没有通过则直接返回验证结果。其中ModelMetadata的GetValidators的实现如下:return ModelValidatorProviders.Providers.GetValidators(this, context);ModelValidatorProviders的定义如下:

 public static class ModelValidatorProviders {

        private static readonly ModelValidatorProviderCollection _providers = new ModelValidatorProviderCollection() {
new DataAnnotationsModelValidatorProvider(),
new DataErrorInfoModelValidatorProvider(),
new ClientDataTypeModelValidatorProvider()
}; public static ModelValidatorProviderCollection Providers {
get {
return _providers;
}
} }

所以这里的GetValidators实际上就是调用Providers里面的每个GetValidators方法,这里我们可以添加自己验证ModelValidatorProvider,ModelValidatorProviders.Providers.Add(new xxxx());

这里验证结束后,我们的参数绑定也就结束了。

相信大家现在多我们自定义数据类型的绑定已经有一个基本的了解了吧。

asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证的更多相关文章

  1. asp.net mvc源码分析-ModelValidatorProviders 客户端的验证

    几年写过asp.net mvc源码分析-ModelValidatorProviders 当时主要是考虑mvc的流程对,客户端的验证也只是简单的提及了一下,现在我们来仔细看一下客户端的验证. 如图所示, ...

  2. ASP.NET MVC源码分析

    MVC4 源码分析(Visual studio 2012/2013) HttpModule中重要的UrlRoutingModule 9:this.OnApplicationPostResolveReq ...

  3. ASP.NET MVC 源码分析(一)

    ASP.NET MVC 源码分析(一) 直接上图: 我们先来看Core的设计: 从项目结构来看,asp.net.mvc.core有以下目录: ActionConstraints:action限制相关 ...

  4. asp.net MVC 源码分析

    先上一张图吧 asp.net请求机制的图  by传智播客邹华栋老师 然后是 邹老师添加MVC请求过程的图 其实MVC 是在.netframework上加了一个过滤器  HttpModule 在C:\W ...

  5. asp.net mvc源码分析-Action篇 IModelBinder

    我们首先还是看看ReflectedParameterBindingInfo的Binder属性吧: public override IModelBinder Binder {            ge ...

  6. asp.net mvc源码分析-Route的GetRouteData

    我知道Route这里东西应该算路由,这里把它放到mvc里面有些不怎么合适,但是我想大家多数遇到路由都是在mvc的时候吧.首先我们还是来看看GetRouteData方法吧 [csharp] public ...

  7. ASP.NET MVC源码分析系列

    Controller下的JsonResult的ExecuteResult方法 public override void ExecuteResult(ControllerContext context) ...

  8. ASP.NET MVC 源码分析(二) —— 从 IRouteBuilder认识路由构建

    我们来看IRouteBuilder的定义: public interface IRouteBuilder { IRouter DefaultHandler { get; set; } IService ...

  9. ASP.NET WebForm / MVC 源码分析

    浏览器 Url:https//localhost:6565/Home/Index ,https//localhost:6565/WebForm1.aspx,请求服务器(构建请求报文,并且将请求报文发送 ...

随机推荐

  1. Linux内核中ioremap映射的透彻理解

    几乎每一种外设都是通过读写设备上的寄存器来进行的,通常包括控制寄存器.状态寄存器和数据寄存器三大类,外设的寄存器通常被连续地编址.根据CPU体系结构的不同,CPU对IO端口的编址方式有两种: (1)I ...

  2. python描述符descriptor(一)

    Python 描述符是一种创建托管属性的方法.每当一个属性被查询时,一个动作就会发生.这个动作默认是get,set或者delete.不过,有时候某个应用可能会有 更多的需求,需要你设计一些更复杂的动作 ...

  3. Hbase常用操作

    下面我们看看HBase Shell的一些基本操作命令,我列出了几个常用的HBase Shell命令,如 名称 命令表达式 创建表 create '表名称', '列名称1','列名称2','列名称N' ...

  4. PE文件结构整理

    一直想做一个PE结构的总结,只是学的时候有很多东西就没搞懂,加上时间一长,很多知识也早忘了,也就一直没完成.这几天从头看了下,好不容易理清楚了,整理一下,以免又忘了 pe文件框架结构,图片贴过来太模糊 ...

  5. hdu 5274 树链剖分

    Dylans loves tree Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Othe ...

  6. 执行Spark运行在yarn上的命令报错 spark-shell --master yarn-client

    1.执行Spark运行在yarn上的命令报错 spark-shell --master yarn-client,错误如下所示: // :: ERROR SparkContext: Error init ...

  7. stdio.h cstdio的区别

    stdio.h  cstdio  string.h  cstring math.h cmath .h是c语言的习惯,在c++中,替换为在前面加个c

  8. 洛谷P3237 米特运输 [HNOI2014] hash/二进制分解

    正解:hash/二进制分解 解题报告: 传送门! umm首先提取下题意趴QAQ 大概是说给一棵树,每个点有一个权值,要求修改一些点的权值,使得同一个父亲的儿子权值相同,且父亲的权值必须是所有儿子权值之 ...

  9. 字母统计-map

    题目描述 输入一行字符串,计算其中A-Z大写字母出现的次数 输入描述: 案例可能有多组,每个案例输入为一行字符串. 输出描述: 对每个案例按A-Z的顺序输出其中大写字母出现的次数. 示例1 输入 复制 ...

  10. tarfile模块可以方操作tar归档文件

    # -*- coding: utf-8 -*- #python 27 #xiaodeng #Python自带的tarfile模块可以方便读取tar归档文件 #http://www.open-open. ...