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 自定义的普通数据类型的绑定和验证的更多相关文章
- asp.net mvc源码分析-ModelValidatorProviders 客户端的验证
几年写过asp.net mvc源码分析-ModelValidatorProviders 当时主要是考虑mvc的流程对,客户端的验证也只是简单的提及了一下,现在我们来仔细看一下客户端的验证. 如图所示, ...
- ASP.NET MVC源码分析
MVC4 源码分析(Visual studio 2012/2013) HttpModule中重要的UrlRoutingModule 9:this.OnApplicationPostResolveReq ...
- ASP.NET MVC 源码分析(一)
ASP.NET MVC 源码分析(一) 直接上图: 我们先来看Core的设计: 从项目结构来看,asp.net.mvc.core有以下目录: ActionConstraints:action限制相关 ...
- asp.net MVC 源码分析
先上一张图吧 asp.net请求机制的图 by传智播客邹华栋老师 然后是 邹老师添加MVC请求过程的图 其实MVC 是在.netframework上加了一个过滤器 HttpModule 在C:\W ...
- asp.net mvc源码分析-Action篇 IModelBinder
我们首先还是看看ReflectedParameterBindingInfo的Binder属性吧: public override IModelBinder Binder { ge ...
- asp.net mvc源码分析-Route的GetRouteData
我知道Route这里东西应该算路由,这里把它放到mvc里面有些不怎么合适,但是我想大家多数遇到路由都是在mvc的时候吧.首先我们还是来看看GetRouteData方法吧 [csharp] public ...
- ASP.NET MVC源码分析系列
Controller下的JsonResult的ExecuteResult方法 public override void ExecuteResult(ControllerContext context) ...
- ASP.NET MVC 源码分析(二) —— 从 IRouteBuilder认识路由构建
我们来看IRouteBuilder的定义: public interface IRouteBuilder { IRouter DefaultHandler { get; set; } IService ...
- ASP.NET WebForm / MVC 源码分析
浏览器 Url:https//localhost:6565/Home/Index ,https//localhost:6565/WebForm1.aspx,请求服务器(构建请求报文,并且将请求报文发送 ...
随机推荐
- MemCache分布式内存对象缓存系统
MemCache超详细解读 MemCache是一个自由.源码开放.高性能.分布式的分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而 ...
- 左右sqlplus一些方法用于汇总
SQL> select * from v$version where rownum=1; BANNER --------------------------------------------- ...
- Linux-2.6.32内核编译流量计数器nfacct
最近一直想看到一本书<一个Jiegeng华>.而技术不依赖书.但是,这并不表示我IT技术没有兴趣.事实证明,,当我无法理解的沧桑.肮脏的领导者无法理解的心理.自我可惜无法理解它处处感受到脏 ...
- 人活系列Streetlights (秩)
人活着系列之Streetlights Time Limit: 1000MS Memory limit: 65536K 题目描写叙述 人活着假设是为了家庭,亲情----能够说是在这个世界上最温暖人心的, ...
- 无法Debug SQL: Unable to start T-SQL Debugging. Could not attach to SQL Server process on
今天SSMS debug SQL当脚本,突然错误: Unable to start T-SQL Debugging. Could not attach to SQL Server process on ...
- 第五十九届冠军(使用C++设计一个类不能被继承)
称号:使用C++设计一个类不能被继承. 分析:这是Adobe 公司2007 的笔试题最新校园招聘. 应聘者的C++基本功底外,还能考察反应能力,是一道非常好的题目. 分析:C++中父类的构造函数会调用 ...
- UVA 1364 - Knights of the Round Table (获得双连接组件 + 二部图推理染色)
尤其是不要谈了些什么,我想A这个问题! FML啊.....! 题意来自 kuangbin: 亚瑟王要在圆桌上召开骑士会议.为了不引发骑士之间的冲突. 而且可以让会议的议题有令人惬意的结果,每次开会前都 ...
- hdu4453 Looploop 2012年杭州现场赛 Splay
题意:维护一个圈,实现六个功能,给某位置起的一些数增加某值,反转某一段数,添加删除某些数,移动当前所指的位置, 简单的splay,把圈拆成链,对于每种操作,处理一下. #define inf 0x3f ...
- Linux netstat订购具体解释
简单介绍 Netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics).masquerade 连接.多播成员 (Multicast Memb ...
- 从源码看Android中sqlite是怎么读DB的(转)
执行query 执行SQLiteDatabase类中query系列函数时,只会构造查询信息,不会执行查询. (query的源码追踪路径) 执行move(里面的fillwindow是真正打开文件句柄并分 ...