MVC数据验证原理及自定义ModelValidatorProvider实现无编译修改验证规则和错误信息

Asp.net MVC中的提供非常简单易用的数据验证解决方案. 通过System.ComponentModel.DataAnnotations提供的很多的验证规则(Required, StringLength等)。但是常常有这样的需求,我们希望能够把model的验证规则,保存到数据或者xml文件中,而不是代码里, 这样的好处是,我们可以很方便的修改验证规则和错误消息,避免需要重新发布网站。

这篇文章,一起来看看是如何通过自定义ModelValidatorProvider来通过XML文件配置对于Model的验证。

阅读目录:

一、简单回顾内置MVC验证的使用

二、分析MVC验证的内部过程

三、一个例子,针对ContactInfo的验证

四、具体实现和应用XmlModelValidatorProvider

一,简单回顾内置MVC验证的使用

下面是典型的MVC验证规则的代码和页面展示效果。

 

二,分析MVC验证的内部过程

1. 实际做验证的是ModelValidator

当我们如上图,在为Person类添加了各种验证规则的dataannotation attributes后,实际操刀来做验证的是DataAnnotationsModelValidator类.
DataAnnotationsModelValidator继承自抽象类ModelValidator,实现了抽象方法Validate, 在该方法中根据Person类中定义的验证规则,对于所有Person的实例进行验证,同时返回一个ModelValidationResult的集合。

2. ModelValidator是由ModelValidatorProviders提供的

MVC在验证过程中使用到的ModelValidator又是由ModelValidatorProviders类提供的, ModelValidatorProviders是一个抽象类,有个抽象方法GetValidators.

该类的定义是这样的

namespace System.Web.Mvc
{
public abstract class ModelValidatorProvider
{
public abstract IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context);
}
}

实际运行中,MVC使用的是继承于ModelValidatorProvider, 实现了GetValidators方法的DataAnnotationsModelValidatorProvider类.

3. MVC的验证过程中可以存在多个ModelValidatorProvider

MVC中可以多个ModelValidatorProvider同时起作用, 他们的效果可以叠加。我们可以使用默认的根据Attribute来定义验证规则的DataAnnotationsModelValidatorProvider,也还可以同时使用我们下面的XmlModelValidatorProvider从xml文件中获取验证规则来做验证。

三,一个例子,针对ContactInfo的验证

下面这个ContactInfo类是我们用来做实际验证的,包含了一些常用的典型的验证,Required, Email, Url等

public class ContactInfo
{
public string FirstName { get; set; }
public string LastName{get;set;}
public string Email{get;set;}
public string Url{get;set;}
}

下面的这个xml文件,定义的是关于ContactInfo类的验证规则,以前我们是写在ContactInfo类中的,现在把它分离出来,放到Content\Validation\Rules\ContactInfo.xml文件中.

这里的message中的值只是一个message的key, message的具体内容放在另外一个xml文件中。

<?xml version="1.0" encoding="utf-8" ?>
<model>
<validator property="FirstName" type="Required" message="FirstName_Required" />
<validator property="FirstName" type="StringLength" arg-int="50" message="FirstName_Length" />
<validator property="LastName" type="Required" message="LastName_Required" />
<validator property="LastName" type="StringLength" arg-int="255" message="LastName_Length" />
<validator property="Email" type="Required" message="Email_Required" />
<validator property="Email" type="StringLength" arg-int="255" message="Email_Length" />
<validator property="Email" type="RegularExpression" arg="^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$" message="Email_RegularExpression" />
<validator property="Url" type="StringLength" arg-int="255" message="Url_Length" />
<validator property="Url" type="RegularExpression" arg="(http://)?(www\.)?\w+\.(com|net|edu|org)" message="Url_RegularExpression" />
</model>

存放message的是Content\Validation\Messages\ContactInfo.xml文件

<?xml version="1.0" encoding="utf-8" ?>
<messages>
<!-- filed error message -->
<message key="FirstName_Required" text="The Frist Name field is required."></message>
<message key="FirstName_Length" text="The field maximum length is 50"></message>
<message key="LastName_Required" text="The Last Name field is required."></message>
<message key="LastName_Length" text="The field maximum length is 255"></message>
<message key="Email_Required" text="The Email field is required."></message>
<message key="Email_Length" text="The field maximum length is 255"></message>
<message key="Email_RegularExpression" text="Invalid email."></message>
<message key="Url_Length" text="The field maximum length is 255"></message>
<message key="Url_RegularExpression" text="Invalid URL."></message>
</messages>

四,具体实现和应用XmlModelValidatorProvider

下面是我们最重要的XmlModelValidatorProvider的实现代码

public class XmlModelValidatorProvider : ModelValidatorProvider
{
// 用来保存System.ComponentModel.DataAnnotations中已经存在的验证规则,也就是MVC自带的Required等验证规则, 因为我们只是验证规则的"源"不一样,一个是代码中,一个是xml,但是验证过程是一样的,所以要重用MVC中的已经写好的验证。
private readonly Dictionary<string, Type> _validatorTypes; private readonly string _xmlFolderPath = HttpContext.Current.Server.MapPath("~//Content//Validation//Rules"); public XmlModelValidatorProvider()
{
_validatorTypes = Assembly.Load("System.ComponentModel.DataAnnotations").GetTypes()
.Where(t => t.IsSubclassOf(typeof (ValidationAttribute)))
.ToDictionary(t => t.Name, t => t);
} #region Stolen from DataAnnotationsModelValidatorProvider
// delegate that converts ValidationAttribute into DataAnnotationsModelValidator
internal static DataAnnotationsModelValidationFactory DefaultAttributeFactory =
(metadata, context, attribute) => new DataAnnotationsModelValidator(metadata, context, attribute); internal static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories = new Dictionary
<Type, DataAnnotationsModelValidationFactory>
{
{
typeof (RangeAttribute),
( metadata, context, attribute)
=>
new RangeAttributeAdapter (metadata, context, ( RangeAttribute ) attribute)
},
{
typeof (RegularExpressionAttribute),
( metadata, context, attribute)
=>
new RegularExpressionAttributeAdapter (metadata, context, ( RegularExpressionAttribute ) attribute)
},
{
typeof (RequiredAttribute),
( metadata, context, attribute) =>
new RequiredAttributeAdapter (metadata, context, ( RequiredAttribute ) attribute)
},
{
typeof (StringLengthAttribute),
( metadata, context, attribute) =>
new StringLengthAttributeAdapter (metadata, context, ( StringLengthAttribute ) attribute)
}
}; #endregion
// 重写GetValidators方法,该从xml文件中获取。根据xml的配置,返回对应的Validator集合
public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context)
{
var results = new List<ModelValidator>(); // whether the validation is for a property or model
// (remember we can apply validation attributes to a property or model and same applies here as well)
var isPropertyValidation = metadata.ContainerType != null && !String.IsNullOrEmpty(metadata.PropertyName); var rulesPath = String.Format("{0}\\{1}.xml", _xmlFolderPath,
isPropertyValidation ? metadata.ContainerType.Name : metadata.ModelType.Name); var rules = File.Exists(rulesPath)
? XElement.Load(rulesPath).XPathSelectElements(String.Format(
"./validator[@property='{0}']",
isPropertyValidation ? metadata.PropertyName : metadata.ModelType.Name)).ToList()
: new List<XElement>(); // Produce a validator for each validation attribute we find
foreach (var rule in rules)
{
DataAnnotationsModelValidationFactory factory; var validatorType = _validatorTypes[String.Concat(rule.Attribute("type").Value, "Attribute")]; if (!AttributeFactories.TryGetValue(validatorType, out factory))
{
factory = DefaultAttributeFactory;
} var validator = (ValidationAttribute) Activator.CreateInstance(validatorType, GetValidationArgs(rule));
validator.ErrorMessage = rule.Attribute("message") != null &&
!String.IsNullOrEmpty(rule.Attribute("message").Value)
? GetValidationMessage(isPropertyValidation ? metadata.ContainerType.Name : metadata.ModelType.Name, rule.Attribute("message").Value)
: null;
results.Add(factory(metadata, context, validator));
} return results;
} private string GetValidationMessage(string model, string key)
{
return MessageProvider.GetViewModelValidationMessage(model, key);
} // read the arguments passed to the validation attribute and cast it their respective type.
private object[] GetValidationArgs(XElement rule)
{
var validatorArgs = rule.Attributes().Where(a => a.Name.ToString().StartsWith("arg"));
var args = new object[validatorArgs.Count()];
var i = 0; foreach (var arg in validatorArgs)
{
var argName = arg.Name.ToString();
var argValue = arg.Value; if (!argName.Contains("-"))
{
args[i] = argValue;
}
else
{
var argType = argName.Split('-')[1]; switch (argType)
{
case "int":
args[i] = int.Parse(argValue);
break; case "datetime":
args[i] = DateTime.Parse(argValue);
break; case "char":
args[i] = Char.Parse(argValue);
break; case "double":
args[i] = Double.Parse(argValue);
break; case "decimal":
args[i] = Decimal.Parse(argValue);
break; case "bool":
args[i] = Boolean.Parse(argValue);
break; default:
args[i] = argValue;
break;
}
}
} return args;
}
}

最后,在Global.cs文件中,把XmlModelValidatorProvider添加到MVC的ModelValidatorProvidersCollection中

ModelValidatorProviders.Providers.Add(new XmlModelValidatorProvider());

自定义ModelValidatorProvider的更多相关文章

  1. MVC数据验证原理及自定义ModelValidatorProvider实现无编译修改验证规则和错误信息

    Asp.net MVC中的提供非常简单易用的数据验证解决方案. 通过System.ComponentModel.DataAnnotations提供的很多的验证规则(Required, StringLe ...

  2. ASP.NET MVC自定义Numberic属性的验证信息

    最近在使用MVC4时碰到一个Model验证的问题:整型属性输入非整型字符串时,错误信息总是“字段 XXX 必须是一个数字”,我总觉得这句话读起来很别扭,所以就萌生了要改变这个默认错误提示信息的念头,但 ...

  3. ASP.NET MVC Model验证(五)

    ASP.NET MVC Model验证(五) 前言 上篇主要讲解ModelValidatorProvider 和ModelValidator两种类型的自定义实现, 然而在MVC框架中还给我们提供了其它 ...

  4. ASP.NET MVC Model验证(四)

    ASP.NET MVC Model验证(四) 前言 本篇主要讲解ModelValidatorProvider 和ModelValidator两种类型的自定义实现,前者是Model验证提供程序,而Mod ...

  5. ASP.NET MVC Model验证(三)

    ASP.NET MVC Model验证(三) 前言 上篇中说到在MVC框架中默认的Model验证是在哪里验证的,还讲到DefaultModelBinder类型的内部执行的示意图,让大家可以看到默认的M ...

  6. ASP.NET MVC Model验证(二)

    ASP.NET MVC Model验证(二) 前言 上篇内容演示了一个简单的Model验证示例,然后在文中提及到Model验证在MVC框架中默认所处的位置在哪?本篇就是来解决这个问题的,并且会描述一下 ...

  7. ASP.NET MVC Model验证(一)

    ASP.NET MVC Model验证(一) 前言 前面对于Model绑定部分作了大概的介绍,从这章开始就进入Model验证部分了,这个实际上是一个系列的Model的绑定往往都是伴随着验证的.也会在后 ...

  8. ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上

    原文:ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上 ASP.NET MVC默认采用基于标准特性的Model验证机制,但是只有应用在Model ...

  9. ASP.NET MVC基于标注特性的Model验证:一个Model,多种验证规则

    原文:ASP.NET MVC基于标注特性的Model验证:一个Model,多种验证规则 对于Model验证,理想的设计应该是场景驱动的,而不是Model(类型)驱动的,也就是对于同一个Model对象, ...

随机推荐

  1. 带你轻松玩转Git--瞬间创建本地仓库

    在上一篇文章中我们对版本控制有了一个比较宏观的了解,同时也能够看到Git 所处在的历史地位.并且对版本控制系统的体系进行了一个宏观的对比,貌似让读者看起来挺复杂的样子. 笔者将会尽可能的简单向大家分享 ...

  2. 各大oj题目分类(转)

    POJ题目分类 | POJ题目分类 | HDU题目分类 | ZOJ题目分类 | SOJ题目分类 | HOJ题目分类 | FOJ题目分类 | 模拟题: POJ1006POJ1008POJ1013POJ1 ...

  3. NYoj WAJUEJI which home strong!(简单搜索)

    题目链接:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=1100 这道题,自己初写搜索,给学长气的只打我,Orz....... 搜索的思路要理 ...

  4. Android网络通信android-async-http入门

    android-async-http入门 门免费链接分享前:http://pan.baidu.com/s/1mg9SvgO 密码:cgg7 API原文:http://loopj.com/android ...

  5. XML的序列化和反序列化 详细介绍

    为什么要做序列化和反序列化? 一个回答: 我们都知道对象是不能在网络中直接传输的,不过还有补救的办法.XML(Extensible Markup Language)可扩展标记语言,本身就被设计用来存储 ...

  6. Linux svnserver存储路径和文件的详细解释

    svn有两种存储方式:BDB和FSFS,眼下用的最多的是FSFS方式,这样的方式的话.通常是存储在\db\revs目录下,里面有一堆以版本命名的文件.如:0.1.2.3.4......,那个就是了 比 ...

  7. request的setAttribute()怎么用的?

    request.setAttribute()怎么用的?JSP1代码String [] test=new String[2];test[0]="1";test[1]="2& ...

  8. WebBrowser控件应用:播放PPT文件

    原文:WebBrowser控件应用:播放PPT文件 一开始想的是用webform来做,用iframe加载文件,把ppt文件另存成htm,然后播放. 可是后来发现,的程序不大容易控制,所以改用winfo ...

  9. 左右lcm,gcd一些性质

    两个整数a,b  他们的最大公约数为n  最小公倍数为m  则有 a,b都能分解为有限个素数的积               12 = 2^2 * 3^1 * 5^0 , 30 = 2^1 * 3^1 ...

  10. datatables表格

    datatables表格 并不是所有的后台开发都有美工和前端工程师来配合做页面,为了显示数据并有一定的美感,jQuery的DataTables插件对于像我这样的前端菜鸟来说真是雪中送炭,当然对于专业的 ...