原文: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类中从写了。

  1. protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) {
  2. List<Attribute> attributeList = new List<Attribute>(attributes);
  3. DisplayColumnAttribute displayColumnAttribute = attributeList.OfType<DisplayColumnAttribute>().FirstOrDefault();
  4. DataAnnotationsModelMetadata result = new DataAnnotationsModelMetadata(this, containerType, modelAccessor, modelType, propertyName, displayColumnAttribute);
  5.  
  6. // Do [HiddenInput] before [UIHint], so you can override the template hint
  7. HiddenInputAttribute hiddenInputAttribute = attributeList.OfType<HiddenInputAttribute>().FirstOrDefault();
  8. if (hiddenInputAttribute != null) {
  9. result.TemplateHint = "HiddenInput";
  10. result.HideSurroundingHtml = !hiddenInputAttribute.DisplayValue;
  11. }
  12.  
  13. // We prefer [UIHint("...", PresentationLayer = "MVC")] but will fall back to [UIHint("...")]
  14. IEnumerable<UIHintAttribute> uiHintAttributes = attributeList.OfType<UIHintAttribute>();
  15. UIHintAttribute uiHintAttribute = uiHintAttributes.FirstOrDefault(a => String.Equals(a.PresentationLayer, "MVC", StringComparison.OrdinalIgnoreCase))
  16. ?? uiHintAttributes.FirstOrDefault(a => String.IsNullOrEmpty(a.PresentationLayer));
  17. if (uiHintAttribute != null) {
  18. result.TemplateHint = uiHintAttribute.UIHint;
  19. }
  20.  
  21. DataTypeAttribute dataTypeAttribute = attributeList.OfType<DataTypeAttribute>().FirstOrDefault();
  22. if (dataTypeAttribute != null) {
  23. result.DataTypeName = dataTypeAttribute.ToDataTypeName();
  24. }
  25.  
  26. EditableAttribute editable = attributes.OfType<EditableAttribute>().FirstOrDefault();
  27. if (editable != null) {
  28. result.IsReadOnly = !editable.AllowEdit;
  29. }
  30. else {
  31. ReadOnlyAttribute readOnlyAttribute = attributeList.OfType<ReadOnlyAttribute>().FirstOrDefault();
  32. if (readOnlyAttribute != null) {
  33. result.IsReadOnly = readOnlyAttribute.IsReadOnly;
  34. }
  35. }
  36.  
  37. DisplayFormatAttribute displayFormatAttribute = attributeList.OfType<DisplayFormatAttribute>().FirstOrDefault();
  38. if (displayFormatAttribute == null && dataTypeAttribute != null) {
  39. displayFormatAttribute = dataTypeAttribute.DisplayFormat;
  40. }
  41. if (displayFormatAttribute != null) {
  42. result.NullDisplayText = displayFormatAttribute.NullDisplayText;
  43. result.DisplayFormatString = displayFormatAttribute.DataFormatString;
  44. result.ConvertEmptyStringToNull = displayFormatAttribute.ConvertEmptyStringToNull;
  45.  
  46. if (displayFormatAttribute.ApplyFormatInEditMode) {
  47. result.EditFormatString = displayFormatAttribute.DataFormatString;
  48. }
  49.  
  50. if (!displayFormatAttribute.HtmlEncode && String.IsNullOrWhiteSpace(result.DataTypeName)) {
  51. result.DataTypeName = DataTypeUtil.HtmlTypeName;
  52. }
  53. }
  54.  
  55. ScaffoldColumnAttribute scaffoldColumnAttribute = attributeList.OfType<ScaffoldColumnAttribute>().FirstOrDefault();
  56. if (scaffoldColumnAttribute != null) {
  57. result.ShowForDisplay = result.ShowForEdit = scaffoldColumnAttribute.Scaffold;
  58. }
  59.  
  60. DisplayAttribute display = attributes.OfType<DisplayAttribute>().FirstOrDefault();
  61. string name = null;
  62. if (display != null) {
  63. result.Description = display.GetDescription();
  64. result.ShortDisplayName = display.GetShortName();
  65. result.Watermark = display.GetPrompt();
  66. result.Order = display.GetOrder() ?? ModelMetadata.DefaultOrder;
  67.  
  68. name = display.GetName();
  69. }
  70.  
  71. if (name != null) {
  72. result.DisplayName = name;
  73. }
  74. else {
  75. DisplayNameAttribute displayNameAttribute = attributeList.OfType<DisplayNameAttribute>().FirstOrDefault();
  76. if (displayNameAttribute != null) {
  77. result.DisplayName = displayNameAttribute.DisplayName;
  78. }
  79. }
  80.  
  81. RequiredAttribute requiredAttribute = attributeList.OfType<RequiredAttribute>().FirstOrDefault();
  82. if (requiredAttribute != null) {
  83. result.IsRequired = true;
  84. }
  85.  
  86. return result;
  87. }

这里的具体创建就不说了,创建了一个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的实现就比较复杂了。

  1. protected virtual void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) {
  2. // need to skip properties that aren't part of the request, else we might hit a StackOverflowException
  3. string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name);
  4. if (!bindingContext.ValueProvider.ContainsPrefix(fullPropertyKey)) {
  5. return;
  6. }
  7.  
  8. // call into the property's model binder
  9. IModelBinder propertyBinder = Binders.GetBinder(propertyDescriptor.PropertyType);
  10. object originalPropertyValue = propertyDescriptor.GetValue(bindingContext.Model);
  11. ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name];
  12. propertyMetadata.Model = originalPropertyValue;
  13. ModelBindingContext innerBindingContext = new ModelBindingContext() {
  14. ModelMetadata = propertyMetadata,
  15. ModelName = fullPropertyKey,
  16. ModelState = bindingContext.ModelState,
  17. ValueProvider = bindingContext.ValueProvider
  18. };
  19. object newPropertyValue = GetPropertyValue(controllerContext, innerBindingContext, propertyDescriptor, propertyBinder);
  20. propertyMetadata.Model = newPropertyValue;
  21.  
  22. // validation
  23. ModelState modelState = bindingContext.ModelState[fullPropertyKey];
  24. if (modelState == null || modelState.Errors.Count == 0) {
  25. if (OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, newPropertyValue)) {
  26. SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
  27. OnPropertyValidated(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
  28. }
  29. }
  30. else {
  31. SetProperty(controllerContext, bindingContext, propertyDescriptor, newPropertyValue);
  32.  
  33. // Convert FormatExceptions (type conversion failures) into InvalidValue messages
  34. foreach (ModelError error in modelState.Errors.Where(err => String.IsNullOrEmpty(err.ErrorMessage) && err.Exception != null).ToList()) {
  35. for (Exception exception = error.Exception; exception != null; exception = exception.InnerException) {
  36. if (exception is FormatException) {
  37. string displayName = propertyMetadata.GetDisplayName();
  38. string errorMessageTemplate = GetValueInvalidResource(controllerContext);
  39. string errorMessage = String.Format(CultureInfo.CurrentCulture, errorMessageTemplate, modelState.Value.AttemptedValue, displayName);
  40. modelState.Errors.Remove(error);
  41. modelState.Errors.Add(errorMessage);
  42. break;
  43. }
  44. }
  45. }
  46. }
  47. }

我们首先看看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方法了:

  1. protected virtual void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) {
  2. Dictionary<string, bool> startedValid = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
  3.  
  4. foreach (ModelValidationResult validationResult in ModelValidator.GetModelValidator(bindingContext.ModelMetadata, controllerContext).Validate(null)) {
  5. string subPropertyName = CreateSubPropertyName(bindingContext.ModelName, validationResult.MemberName);
  6.  
  7. if (!startedValid.ContainsKey(subPropertyName)) {
  8. startedValid[subPropertyName] = bindingContext.ModelState.IsValidField(subPropertyName);
  9. }
  10.  
  11. if (startedValid[subPropertyName]) {
  12. bindingContext.ModelState.AddModelError(subPropertyName, validationResult.Message);
  13. }
  14. }
  15. }

这个方法意思很简单,验证数据的有效性。我们先看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方法:

  1. public override IEnumerable<ModelValidationResult> Validate(object container) {
  2. bool propertiesValid = true;
  3.  
  4. foreach (ModelMetadata propertyMetadata in Metadata.Properties) {
  5. foreach (ModelValidator propertyValidator in propertyMetadata.GetValidators(ControllerContext)) {
  6. foreach (ModelValidationResult propertyResult in propertyValidator.Validate(Metadata.Model)) {
  7. propertiesValid = false;
  8. yield return new ModelValidationResult {
  9. MemberName = DefaultModelBinder.CreateSubPropertyName(propertyMetadata.PropertyName, propertyResult.MemberName),
  10. Message = propertyResult.Message
  11. };
  12. }
  13. }
  14. }
  15.  
  16. if (propertiesValid) {
  17. foreach (ModelValidator typeValidator in Metadata.GetValidators(ControllerContext)) {
  18. foreach (ModelValidationResult typeResult in typeValidator.Validate(container)) {
  19. yield return typeResult;
  20. }
  21. }
  22. }
  23. }

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

  1. public static class ModelValidatorProviders {
  2.  
  3. private static readonly ModelValidatorProviderCollection _providers = new ModelValidatorProviderCollection() {
  4. new DataAnnotationsModelValidatorProvider(),
  5. new DataErrorInfoModelValidatorProvider(),
  6. new ClientDataTypeModelValidatorProvider()
  7. };
  8.  
  9. public static ModelValidatorProviderCollection Providers {
  10. get {
  11. return _providers;
  12. }
  13. }
  14.  
  15. }

所以这里的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. 《学习opencv》笔记——矩阵和图像处理——cvGEMM,cvGetCol,cvGetCols and cvGetDiag

    矩阵和图像操作 (1)cvGEMM函数 其结构 double cvGEMM(//矩阵的广义乘法运算 const CvArr* src1,//乘数矩阵 const CvArr* src2,//乘数矩阵 ...

  2. JDNI

    JNDI是为了一个最最核心的问题:是为了解耦,是为了开发出更加可维护.可扩展的系统JNDI和JDBC起的作用类似:JDBC(Java Data Base Connectivity,java数据库连接) ...

  3. 为大型数据文件每行只能产生id

    为大型数据文件每行只能产生id 4个主要思路: 1 单线程处理 2 普通多线程 3 hive 4 Hadoop 搜到一些參考资料 <Hadoop实战>的笔记-2.Hadoop输入与输出 h ...

  4. error LNK2019: 解析的外部符号 __imp__DispatchMessageW@4,在函数的符号 _WinMain@16 据引述

    错误: 1>WinMain.obj : error LNK2019: 解析的外部符号 __imp__DispatchMessageW@4,在函数的符号 _WinMain@16 据引述 1> ...

  5. Quartus II 11.0破发点(不同的是低版本号)

    小订单: 近期用到了黑金的altera飓风4带的开发板,套件里面带的Quartus II软件版本号为11.0,之前所用版本号为9.1,所以打算吧11.0版本号也安装一下.没想到这个破解的过程让我属实蛋 ...

  6. 大虾翻译(一):jQuery.extend()

    本文是在JavaScript之三里面链接内容的中文翻译.我会尽可能做到信达雅且保持作者原意不变,OK,let's Go! jQuery.extend(target,[object1],[objectN ...

  7. Android.mk中的经常使用语法

    Android.mk编译文件是用来向Android NDK描写叙述你的C,C++源码文件的, 今天查了一些经常使用的的语法. 一 概述: 一个Android.mk文件用来向编译系统描写叙述你的源码. ...

  8. 左右v$datafile和v$tempfile中间file#

    v$datafile关于存储在文件中的数据视图的信息,v$tempfile查看存储在一个临时文件中的信息. 有两种观点file#现场,首先来看看官方文件的定义: V$DATAFILE This vie ...

  9. Android Studio 100 tips and tricks

    关于本文 本文是想总结一些Android Studio的使用技巧,对于大多数习惯了使用eclipse的人来说,可能会须要一段时间,可是假设看过以下的一些介绍,你就能体会到Android Studio的 ...

  10. Telephone Lines USACO 月赛

    以前做过这套题目 这个题又重新写了:http://www.cnblogs.com/jh818012/archive/2013/05/05/3182681.html 还是以前的思路 一直错在一个地方:决 ...