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

原文: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 自定义的普通数据类型的绑定和验证

时间: 2024-10-10 01:31:41

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

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

几年写过asp.net mvc源码分析-ModelValidatorProviders 当时主要是考虑mvc的流程对,客户端的验证也只是简单的提及了一下,现在我们来仔细看一下客户端的验证. 如图所示, 首先我们要知道这里的data-val这些属性是在哪里生成的?可以肯定是在mvc后台生成的, @Html.PasswordFor(m => m.Password) 生成input @Html.ValidationMessageFor(m => m.Password) 生成span 调用层级关系:

ASP.NET MVC 源码分析(一)

ASP.NET MVC 源码分析(一) 直接上图: 我们先来看Core的设计: 从项目结构来看,asp.net.mvc.core有以下目录: ActionConstraints:action限制相关 AntiForgery:防伪相关 ActionResults:action返回对象相关 ApiExplorer:API描述和元数据相关接口 ApplicationModels:应用程序模型相关,应该是全局的model Areas:地区标签 Filters:大名鼎鼎的过滤器组件 Formatters:

asp.net MVC 源码分析

先上一张图吧 asp.net请求机制的图  by传智播客邹华栋老师 然后是 邹老师添加MVC请求过程的图 其实MVC 实在.netframework上加了一个过滤器  HttpModule 在C:\Windows\Microsoft.NET\Framework\v4.0.30319 下的配置文件里加入的  <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" /&

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

我们首先还是看看ReflectedParameterBindingInfo的Binder属性吧: public override IModelBinder Binder {            get {                IModelBinder binder = ModelBinders.GetBinderFromAttributes(_parameterInfo,                    () => String.Format(CultureInfo.Curre

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

我们来看IRouteBuilder的定义: public interface IRouteBuilder { IRouter DefaultHandler { get; set; } IServiceProvider ServiceProvider { get; } IList<IRouter> Routes { get; } IRouter Build(); } 一个默认的IRouter对象,一个Build方法,一个IRouter集合和一个获取服务对象IServiceProvider. 我们

ASP.NET MVC源码分析系列

Controller下的JsonResult的ExecuteResult方法 public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } if ((this.JsonRequestBehavior == JsonRequestBehavior.DenyGet) &&

WebForm / MVC 源码分析

ASP.NET WebForm / MVC 源码分析 浏览器 Url:https//localhost:6565/Home/Index ,https//localhost:6565/WebForm1.aspx,请求服务器(构建请求报文,并且将请求报文发送给服务器) 1:服务器(内核模式 Http.sys)对请求报文做基本的处理 2:请求服务器(用户模式,IIS服务器) 2.1:对发送过来的数据做一个检查,如果请求的是静态资源的(.html,jpg,js),那么IIS直接将这些资源返回浏览器 2.

Volley源码分析之自定义MultiPartRequest(文件上传)

本篇内容目录: 使用HttpURLConnection上传文件到服务器案例 自定义支持文件上传的MultiPartRequest Web后台接收文件的部分代码 先来看下HttpURLConnection来文件上传的案例: 1.传送数据到服务器,必定是使用POST请求: //设置请求方式为post httpURLConnection.setDoOutput(true); httpURLConnection.setRequestMethod("POST"); 2.上传文件的HTTP请求中的

Asp.Net MVC源码调试

首先下载MVC源代码,下载地址为:https://aspnetwebstack.codeplex.com/ 打开项目,卸载test文件夹下的所有项目和System.Web.WebPages.Administration这个项目,然后重新编译源代码,重新生成所有项目. 在项目中新建一个空的Web项目,包含基本的MVC组件 删除项目中的如下程序集的引用 添加项目中MVC项目源码的引用 查询MVC源码的版本信息 可以看到当前源码的版本为5.2.4.0 打开新建项目中的所有Web.config文件,修改