自定义ModelValidatorProvider
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的更多相关文章
- MVC数据验证原理及自定义ModelValidatorProvider实现无编译修改验证规则和错误信息
Asp.net MVC中的提供非常简单易用的数据验证解决方案. 通过System.ComponentModel.DataAnnotations提供的很多的验证规则(Required, StringLe ...
- ASP.NET MVC自定义Numberic属性的验证信息
最近在使用MVC4时碰到一个Model验证的问题:整型属性输入非整型字符串时,错误信息总是“字段 XXX 必须是一个数字”,我总觉得这句话读起来很别扭,所以就萌生了要改变这个默认错误提示信息的念头,但 ...
- ASP.NET MVC Model验证(五)
ASP.NET MVC Model验证(五) 前言 上篇主要讲解ModelValidatorProvider 和ModelValidator两种类型的自定义实现, 然而在MVC框架中还给我们提供了其它 ...
- ASP.NET MVC Model验证(四)
ASP.NET MVC Model验证(四) 前言 本篇主要讲解ModelValidatorProvider 和ModelValidator两种类型的自定义实现,前者是Model验证提供程序,而Mod ...
- ASP.NET MVC Model验证(三)
ASP.NET MVC Model验证(三) 前言 上篇中说到在MVC框架中默认的Model验证是在哪里验证的,还讲到DefaultModelBinder类型的内部执行的示意图,让大家可以看到默认的M ...
- ASP.NET MVC Model验证(二)
ASP.NET MVC Model验证(二) 前言 上篇内容演示了一个简单的Model验证示例,然后在文中提及到Model验证在MVC框架中默认所处的位置在哪?本篇就是来解决这个问题的,并且会描述一下 ...
- ASP.NET MVC Model验证(一)
ASP.NET MVC Model验证(一) 前言 前面对于Model绑定部分作了大概的介绍,从这章开始就进入Model验证部分了,这个实际上是一个系列的Model的绑定往往都是伴随着验证的.也会在后 ...
- ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上
原文:ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上 ASP.NET MVC默认采用基于标准特性的Model验证机制,但是只有应用在Model ...
- ASP.NET MVC基于标注特性的Model验证:一个Model,多种验证规则
原文:ASP.NET MVC基于标注特性的Model验证:一个Model,多种验证规则 对于Model验证,理想的设计应该是场景驱动的,而不是Model(类型)驱动的,也就是对于同一个Model对象, ...
随机推荐
- KafkaOffsetMonitor
Kafka实战-KafkaOffsetMonitor 1.概述 前面给大家介绍了Kafka的背景以及一些应用场景,并附带上演示了Kafka的简单示例.然后,在开发的过程当中,我们会发现一些问题,那 ...
- javascript 实现一个网页,然后计算出有多少剩余时间的倒计时程序
function counter() { var date = new Date(); var year = date.getFullYear(); var date2 = new Date(year ...
- Debug和Release的不同
1. 变量. 大家都知道,debug跟release在初始化变量时所做的操作是不同的,debug是将每个字节位都赋成0xcc(注1),而release的赋值近似于随机(我想是直接从内存中分配的,没有 ...
- TempData,ViewData和ViewBag的比较
TempData,ViewData和ViewBag的比较 学习ASP.NET有大约一个月了,一直都是半生不熟的,因为之前的很长时间都是在做java开发,没有时间静下来心来学习,加上ASP.NET的框架 ...
- 【百度地图API1.1】修改文本标注的样式
原文:[百度地图API1.1]修改文本标注的样式 百度地图API1.0中文本标注的样式写法为: label.getDom().style.borderColor = "#808080&quo ...
- 抄360于Launcher浮动窗口的屏幕显示内存使用情况(改进版)
MainActivity例如下列: package cc.cc; import android.os.Bundle; import android.view.View; import android. ...
- jquery自己主动旋转的登录界面的背景代码登录页背景图
在其他网站上看到比较爽Web登录界面.背景图片可以自己主动旋转. 介绍给大家.有兴趣的可以改改下来作为自己的系统登录界面. 如图: watermark/2/text/aHR0cDovL2Jsb2cuY ...
- Java之final、finalize、finally的区别
一.性质不同(1)final为关键字:(2)finalize()为方法:(3)finally为为区块标志,用于try语句中:二.作用(1)final为用于标识常量的关键字,final标识的关键字存储在 ...
- Oracle 11g 客户端的安装和配置。
原文:Oracle 11g 客户端的安装和配置. 数据库和客户端在不同的机器之上. 在安装之前,在安装Oracle数据库的服务器上导航到下面的目录. 将listener.ora和tnsnames.or ...
- 使用 C# 进行 Outlook 2007 编程
原文:使用 C# 进行 Outlook 2007 编程 探讨如何使用 C# 编程语言生成 Outlook 识别的应用程序和 Outlook 外接程序. 请从"Add References&q ...