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. javascript 实现一个网页,然后计算出有多少剩余时间的倒计时程序

    function counter() { var date = new Date(); var year = date.getFullYear(); var date2 = new Date(year ...

  2. hdu 4915 Parenthese sequence--2014 Multi-University Training Contest 5

    主题链接:http://acm.hdu.edu.cn/showproblem.php?pid=4915 Parenthese sequence Time Limit: 2000/1000 MS (Ja ...

  3. UVa 10223 - How many nodes ?

    称号:气你一个整数n,问:多少节点可以产生n不同的二叉树. 分析:数论,卡特兰数.根据定义,你可以. 说明:请参阅http://blog.csdn.net/mobius_strip/article/d ...

  4. React.js终探(五)

    在React中,一切都是看做组件. 而组件的嵌套也是十分常见的. 所以有的组件就作为容器组件 容器组件 React元素可以包含子元素 如 //JSX <ezpanel title="t ...

  5. linux_mac_配置itrem2 rz sz_bug处理

    0:传输 .jar 等文件有问题 是  添加 sz -bye  以二进制流方式传输 1:安装  homebrew 2: brew install lrzsz 3:搜索 iterm2-recv-zmod ...

  6. B二分法

    <span style="color:#330099;">/* B - 二分法 基金会 Time Limit:1000MS Memory Limit:65536KB 6 ...

  7. Linux常用命令3--如何设置IP地址?如何更改系统时间?

    Linux常用命令 系统状态监控 [1]ps:用于显示当前系统中运行的进程. 语法:ps [-option]:常用的参数有:-a;-u;-x;-e;-f. -a:显示所有进程: -u:显示所有用户: ...

  8. hdu - 3049 - Data Processing(乘法逆元)

    题意:N(N<=40000)个数n1, n2, ..., nN (ni<=N),求(2 ^ n1 + 2 ^ n2 + ... + 2 ^nN) / N % 1000003. 题目链接:h ...

  9. [连载]Java程序设计(04)---任务驱动的方法:工资结算系统

    任务:或在公司,该公司将其分为三类人员:部门经理.销售员.在发工资的时候,部门经理拿固定月薪8000元.技术人员按每小时100元领取月薪.销售人员依照500元底薪加当月销售额的4%进行提成.设计并实现 ...

  10. (64位oracle使用32位的PLSQL)安装64位的oracle数据库软件,使用32位的PLSQL Developer连接方法

    因为PLSQL Developer没有提供64位的,于是依据网上的资料做了一下整理,发上来 1.下载并安装Oracle 11g R2 64位,在server上安装时忽略硬件检測失败信息: 2.下载Or ...