在《ASP.NET MVC下的四种验证编程方式》一文中我们介绍了ASP.NET MVC支持的四种服务端验证的编程方式(“手工验证”、“标注ValidationAttribute特性”、“让数据类型实现IValidatableObject或者IDataErrorInfo”),那么在ASP.NET MVC框架内部是如何提供针对这四种不同编程方式的支持的呢?接下来我们就来聊聊这背后的故事。

一、ModelValidator与ModelValidatorProvider

虽然Model绑定的方式因被验证数据类型的差异而有所不同,但是ASP.NET MVC总是使用一个名为ModelValidator的对象来对绑定的数据对象实施验证。所有的ModelValidator类型均继承自具有如下定义的抽象类ModelValidator。它的GetClientValidationRules方法返回一个元素类型为ModelClientValidationRule的集合,而ModelClientValidationRule是对客户端验证规则的封装,我们会在客户端验证部分对其进行详细介绍。

   1: public abstract class ModelValidator

   2: {

   3:     //其他成员

   4:     public virtual IEnumerable<ModelClientValidationRule> GetClientValidationRules();

   5:     public abstract IEnumerable<ModelValidationResult> Validate(object container);

   6:  

   7:     public virtual bool IsRequired { get; }

   8: }

针对目标数据的验证是通过调用Validate方法来完成的,该方法的输入参数container表示的正是被验证的对象。正是因为被验证的总是一个复杂类型的对象,后者又被称为一个具有若干数据成员的“容器”对象,所以对应的参数被命名为container。Validate方法表示验证结果的返回值并不是一个简单的布尔值,而是一个元素类型为具有如下定义的ModelValidationResult对象集合。

   1: public class ModelValidationResult

   2: {  

   3:     public string MemberName { get; set; }

   4:     public string Message { get; set; }

   5: }

ModelValidationResult具有两个字符串类型属性MemberName和Message,前者代表被验证数据成员的名称,后者表示错误消息。一般来说,如果ModelValidationResult对象来源于针对容器对象本身的验证,它的MemberName属性为空字符串。对于针对容器对象某个属性的验证来说,属性名称会作为返回的ModelValidationResult对象的MemberName属性。

ModelValidationResult集合只有在验证失败的情况下才会返回。如果被验证数据对象符合所有的验证规则,Validate方法会直接返回Null或者一个空ModelValidationResult集合。值得一提的是,我们有时候会用ValidationResult的静态只读字段Success表示成功通过验证的结果,实际上该字段的值就是Null。

   1: public class ValidationResult

   2: {

   3:     //其他成员

   4:     public static readonly ValidationResult Success;

   5: }

ModelValidator具有一个布尔类型的只读属性IsRequired表示该ModelValidator是否对目标数据进行“必需性”验证(即被验证的数据成员必须具有一个具体的值),该属性默认返回False。我们可以通过应用RequiredAttribute特性将某个属性定义成“必需”的数据成员。

我们知道ASP.NET MVC大都采用Provider的模式来提供相应的组件,比如描述Model元数据的ModelMetadata通过对应的ModelMetadataProvider来提供,实现Model绑定的ModelBinder则可以通过对应的ModelBinderProvider来提供,用于实现Model验证的ModelValidator也不例外,它对应的提供者为ModelValidatorProvider,对应的类型继承自具有如下定义的抽象类ModelValidator Provider。

   1: public abstract class ModelValidatorProvider

   2: {

   3:     public abstract IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);

   4: }

如上面的代码片段所示,GetValidators方法具有两个参数,一个是用于描述被验证类型或者属性Model元数据的ModelMetadata对象,另一个是当前ControllerContext。该方法返回的是一个元素类型为ModelValidator的集合。

ASP.NET MVC 通过静态类型ModelValidatorProviders对使用的ModelValidatorProvider进行注册。如下面的代码片段所示,ModelValidatorProviders具有一个静态只读属性Providers,对应的类型为ModelValidatorProviderCollection,它表示基于整个Web应用范围的全局ModelValidatorProvider集合。

   1: public static class ModelValidatorProviders

   2: {   

   3:     public static ModelValidatorProviderCollection Providers { get; }

   4: }

   5:  

   6: public class ModelValidatorProviderCollection : Collection<ModelValidatorProvider>

   7: {   

   8:     public ModelValidatorProviderCollection();

   9:     public ModelValidatorProviderCollection(IList<ModelValidatorProvider> list);

  10:     public IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);   

  11: }

值得一提的是用于描述Model元数据的ModelMetadata类型具有如下一个GetValidators方法,它返回的ModelValidator列表正是利用注册到ModelValidatorProviders静态属性Providers上的ModelValidatorProvider创建的。

   1: public class ModelMetadata

   2: {

   3:     //其他成员

   4:     public virtual IEnumerable<ModelValidator> GetValidators(ControllerContext context);

   5: }

如右图所示的UML列出了组成Model验证系统的三个核心类型。具体的Model验证工作总是通过某个具体的ModelValidator来完成,作为ModelValidator提供者的ModelValidatorProvider注册在静态类型ModelValidatorProviders之上。

二、DataAnnotationsModelValidator

我们在《ASP.NET MVC下的四种验证编程方式》中介绍了三种不同的“自动化验证”的编程方式,ASP.NET MVC在内部会采用不同的ModelValidator来对绑定的参数实施验证。一个具体的ModelValidator通常有相应的ModelValidatorProvider来提供,接下来的内容中将对ASP.NET MVC提供的原生的ModelValidator和对应的ModelValidatorProvider作详细的介绍。

对于上面提到的这三种验证编程方式,第一种(利用应用在数据类型或其数据成员上的ValidationAttribute特性来定义相应的验证规则)是最为常用的。基于ValidationAttribute特性这种声明式验证解决方案最终通过DataAnnotationsModelValidator来完成。一个DataAnnotationsModelValidator对象实际上是对一个ValidationAttribute特性的封装,这可以从如下所示的定义看出来。

   1: public class DataAnnotationsModelValidator : ModelValidator

   2: {   

   3:     public DataAnnotationsModelValidator(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute);

   4:     public override IEnumerable<ModelClientValidationRule> GetClientValidationRules();

   5:  

   6:     public override IEnumerable<ModelValidationResult> Validate(object container);

   7:  

   8:     protected internal ValidationAttribute     Attribute { get; }

   9:     protected internal string                  ErrorMessage { get; }

  10:     public override bool                       IsRequired { get; }

  11: }

DataAnnotationsModelValidator的提供者为DataAnnotationsModelValidatorProvider,关于ValidationAttribute、DataAnnotationsModelValidator和DataAnnotationsModelValidatorProvider的详细内容可以参考之前写的三篇文章。

ASP.NET MVC基于标注特性的Model验证:ValidationAttribute

ASP.NET MVC基于标注特性的Model验证:DataAnnotationsModelValidator

ASP.NET MVC基于标注特性的Model验证:DataAnnotationsModelValidatorProvider

三、ValidatableObjectAdapter

如果被验证的数据类型实现了IValidatable接口,ASP.NET MVC会自动调用实现的Validate方法对其实施验证,此时创建的ModelValidator是一个ValidatableObjectAdapter对象。ValidatableObjectAdapter定义如下,其Validate方法的实现逻辑很简单:它直接调用被验证对象的Validate方法,并将返回的ValidationResult对象转换成ModelValidationResult类型。

   1: public class ValidatableObjectAdapter : ModelValidator

   2: {

   3:     public ValidatableObjectAdapter(ModelMetadata metadata, ControllerContext context);

   4:     public override IEnumerable<ModelValidationResult> Validate(object container);

   5: }

虽然ValidatableObjectAdapter继承自ModelValidator,但是ASP.NET MVC貌似没有将其视为一个真正意义上的ModelValidator,而是将其视为一个“适配器(Adapter)”。ASP.NET MVC也没有为ValidatableObjectAdapter定义单独的ModelValidatorProvider,它的提供者其实是上面提到过的DataAnnotationsModelValidatorProvider。

四、DataErrorInfoModelValidator

如果我们让数据类型实现IDataErrorInfo接口,可以利用实现的Error属性和索引提供针对自身以及所属数据成员的验证错误信息。针对这样的数据类型,ASP.NET MVC最终会创建一个DataErrorInfoModelValidator对象来对其实施验证,DataErrorInfoClassModelValidator和DataErrorInfoPropertyModelValidator是两个具体的DataErrorInfoModelValidator。

DataErrorInfoClassModelValidator和DataErrorInfoPropertyModelValidator是两个内部类型。前者针对容器对象自身实施验证,所以它只需要从实现的Error属性中提取错误消息并将其转换成返回的ModelValidationResult对象。后者则专门验证容器对象的某个属性,它在实现的Validate方法中会利用属性名从实现的索引中提取相应的错误消息并将其转换成返回的ModelValidationResult对象。

   1: internal sealed class DataErrorInfoClassModelValidator : ModelValidator

   2: {

   3:     public DataErrorInfoClassModelValidator(ModelMetadata metadata, ControllerContext controllerContext);

   4:     public override IEnumerable<ModelValidationResult> Validate(object container);

   5: }

   6:  

   7: internal sealed class DataErrorInfoPropertyModelValidator : ModelValidator

   8: {

   9:     public DataErrorInfoPropertyModelValidator(ModelMetadata metadata, ControllerContext controllerContext);

  10:     public override IEnumerable<ModelValidationResult> Validate(object container);

  11: }

ASP.NET MVC最终利用具有如下定义的DataErrorInfoModelValidatorProvider来提供这两种类型的DataErrorInfoModelValidator。对于其实现的GetValidators方法来说,如果被验证对象的类型实现了IDataErrorInfo接口,它会创建一个DataErrorInfoClassModelValidator对象并添加到返回的ModelValidator列表中。如果被验证的是容器类型的某个属性值并且容器类型实现了IDataErrorInfo接口,它会创建一个DataErrorInfoPropertyModelValidator对象并添加到返回的ModelValidator列表中。

   1: public class DataErrorInfoModelValidatorProvider : ModelValidatorProvider

   2: {

   3:     public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);

   4: }

ASP.NET MVC下的四种验证编程方式[续篇]的更多相关文章

  1. ASP.NET MVC下的四种验证编程方式[续篇]【转】

    在<ASP.NET MVC下的四种验证编程方式> 一文中我们介绍了ASP.NET MVC支持的四种服务端验证的编程方式(“手工验证”.“标注ValidationAttribute特性”.“ ...

  2. ASP.NET MVC下的四种验证编程方式

    ASP.NET MVC采用Model绑定为目标Action生成了相应的参数列表,但是在真正执行目标Action方法之前,还需要对绑定的参数实施验证以确保其有效性,我们将针对参数的验证成为Model绑定 ...

  3. ASP.NET MVC下的四种验证编程方式【转】

    ASP.NET MVC采用Model绑定为目标Action生成了相应的参数列表,但是在真正执行目标Action方法之前,还需要对绑定的参数实施验证以确保其有效 性,我们将针对参数的验证成为Model绑 ...

  4. Asp.Net Core下的两种路由配置方式

    与Asp.Net Mvc创建区域的时候会自动为你创建区域路由方式不同的是,Asp.Net Core下需要自己手动做一些配置,但更灵活了. 我们先创建一个区域,如下图 然后我们启动访问/Manage/H ...

  5. 在ASP.NET MVC下扩展一个带验证的RadioButtonList

    在ASP.NET MVC4中,HtmlHelper为我们提供了Html.RadioButton()方法用来显示Radio Button单选按钮.如果想显示一组单选按钮,通常的做法是遍历一个集合把每个单 ...

  6. ASP.NET MVC 主要的四种过滤器和三种具体实现类

    4种常用过滤器(IAuthrorizationFilter.IActionFilter.IResultFilter.IExceptionFilter) 和 3种具体实现类(AuthorizeAttri ...

  7. 待实践二:MVC3下的3种验证 (1)前台 jquery validate验证 (2)MVC实体验证 (3)EF生成的/自己手写的 自定义实体校验(伙伴类+元素据共享)

    MVC3下的3种验证 (1):前台Jquery Validate脚本验证 引入脚本 <script src="../js/jquery.js" type="text ...

  8. ASP.NET MVC Model元数据(四)

    ASP.NET MVC Model元数据(四) 前言 前面的篇幅讲解了Model元数据生成的过程,并没有对Model元数据生成过程的内部和Model元数据结构的详细解释.看完本篇后将会对Model元数 ...

  9. SNF快速开发平台MVC-EasyUI3.9之-ueditor富文本编辑在 asp.net MVC下使用步骤

    mvc项目中用到了这个富文本编辑就试着把遇到的问题个使用步骤在这里记录一下,希望大家少走弯路. 1.首先我们先下载net版本的uediot 2.然后把整个文档拷贝到我们的项目中,记得是整个 把下载的文 ...

随机推荐

  1. Erlang error handling

    Erlang error handling Contents Preface try-catch Process link Erlang-way error handling OTP supervis ...

  2. day01-1 常用dos命令

    计算机 = 硬件 + 软件 硬件: cpu 计算机中的运算核心与控制核心.负责了解析计算机的指令与计算机运算的执行. 内存 数据与cpu交互的桥梁.程序所需要的数据都会先加载到内存中.cpu直接读取内 ...

  3. C#按照指定长度分割中英文字符串

    最近有一个需求:玩家发的不同长度文字,需要自适应行数. 初步实现想法很简单,直接获取字符数均分行数,再利用string.substring()切割即可.但是显而易见,由于一般字体下,中文显示宽度一般是 ...

  4. GIT 版本控制常用命令学习汇总

    GIT 版本控制常用命令汇总 git version 查看当前git版本信息 git help 获取全部命令帮助信息 git help <command> 获取指定命令帮助信息 git c ...

  5. Java ArrayList和Vector、LinkedList与ArrayList、数组(Array)和列表集合(ArrayList)的区别

    ArrayList和Vector的区别ArrayList与Vector主要从二方面来说.  一.同步性:   Vector是线程安全的,也就是说是同步的,而ArrayList是线程序不安全的,不是同步 ...

  6. WebService创建与使用

    因为项目中需要实现客户端与服务器端的数据交换,以及获取服务器端其他程序的分析结果,所以对WebService做了些简单的了解,现记录如下: 一.WebService程序编写 1.  在VS中新建空白网 ...

  7. 控制TextField的内容长度

    参考如下代码(下例是控制设置交易密码,控制6位): - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [ ...

  8. 第一章-第十三题(该游戏团队, 有很好的软件,但是商业模式和其他软件之外的因素有没有考虑到?)--By梁旭晖

    这款软件无疑是一个好软件,软件的开发者是有相当水平的,可以说是优秀的软件编写人员,但是也只是优秀的软件人员,术业有专攻,他们在其他方面我觉得是有很大的欠缺的. 我觉得,他们并没有抓住消费者的心理,首先 ...

  9. MapleSim助力长臂挖掘机建模问题解决

    1.问题描述 一家机械零部件设计公司需要一个挖掘机模型,验证他们的零部件是否匹配完整的挖掘机系统.由于他们是一个零部件供应商,公司没有足够的资源和研发人员使用传统的工具创建一个完整系统的详细模型.然而 ...

  10. UI控件(UIImageView)

    @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; image1_ = [UIImage imageNa ...