MVC中的扩展点(九)验证
验证用于保证用户输入的正确性,及时阻止用户提交错误数据,确保数据符合业务规则。
MVC中可直接在控制器Action方法中进行验证:检查传入参数,如果传入参数不符合业务规则,则通过控制器的ModelState属性的AddModelError方法向模型状态添加错误消息,通过ModelState.IsValid判断模型是否全部通过验证,随后,在视图中通过Html.ValidationSummary、Html.ValidationMessage、Html.ValidationMessageFor辅助方法生成验证消息。
在Controller中验证并不是最好的方法:验证过于分散,容易造成重复代码,不利于维护与扩展。验证应该是和模型紧密相关的,如果我们将模型独立为一个单独的程序集,那么验证也应该包含在模型的程序集中。从用户录入的数据到具体的模型,这个映射过程就是模型绑定,所以在绑定过程中实现验证是一个不错的选择,这也是MVC中最重要的验证方式。
MVC默认绑定器(DefaultModelBinder)中包含了验证架构,具体来说,默认绑定器在将值提供器的数据填充到模型的时候,遵循以下流程:调用默认绑定器的OnModelUpdating方法—>从值提供器获取值并填充到模型—>调用默认绑定器的OnModelUpdated方法。DefaultModelBinder类中OnModelUpdating方法始终返回true,即默认绑定器在填充模型之前没有验证,在填充数据后,OnModelUpdated中实现相应的验证。默认绑定器中验证体系类关系图如下:
模型绑定中的验证体系包含三个主要类:
验证器提供者:ModelValidatorProvider,用于产生验证器
验证器:ModelValidator,用于实现具体的验证逻辑,其中GetClientValidationRules用于返回客户端验证脚本使用的验证规则(ModelClientValidationRule),Validate方法用于实现服务端的验证逻辑。
特性:通常由提供者使用,根据特性指定的规则产生相应的验证器
ModelValidatorProviders是一个静态类,他包含一个用于保存MVC中默认验证器提供者的集合(静态属性Proviers),MVC在填充完模型数据之后,依次对每一个模型属性,从Providers中获取所有的针对该属性的验证器,然后调用验证器上的Validate方法产生验证结果(ModelValidationResult),绑定器根据该结果数据向ModelState中添加验证消息。
MVC中实现了三个默认的验证器提供者(相应产生三个验证器):
DataAnnotationsModelValidatorProvider: 用于实现.NET中的验证特性,即System.ComponentModel.DataAnnotations命名空间下的多种验证特性,包含用于限制属性区间的RangeAttribute,用于验证数据是否符合正则表达式的RegularExpressionAttribute,用于指定必需项的RequiredAttribute,用于限制字符串长度的StringLengthAttribute,DataAnnotationsModelValidatorProvider通过适配器模式,将.NET的验证特性转换为ModelValidator,所以我们可以直接在MVC中使用.NET验证特性来实现验证。
DataErrorInfoClassModelValidatorProvider: 此提供器主要是为了向后兼容,用于实现基于IDataErrorInfo接口的验证方式(MVC 1.0),即你可以为模型实现IDataErrorInfo接口,这样默认绑定器同样可以通过该接口来调用你的验证逻辑。
ClientDataTypeModelValidatorProvider: 此提供器只用于客户端对数值型数据的验证(产生相应的客户端验证脚本),他的Validate方法不会返回任何验证结果。
MVC中另外实现了一个抽象类:AssociatedValidatorProvider,它从ModelValidatorProvider继承,并重写了GetValidator方法,增加传入了附加在模型属性上的特性集合。所以,如果我们需要实现基于特性的验证方式,则应该从此类继承实现自己的验证器及相应的提供者类。当然我们也可以使用默认的DataAnnotationsModelValidatorProvider,这样我们只需要从ValidationAttribute特性继承,并实现自己的验证逻辑。
客户端验证
ModelValidator中GetClientValidationRules方法可以返回用于客户端的验证规则,这些规则可以在客户端脚本中访问,客户端脚本根据这些验证规则检查用户录入资料,并将验证结果反馈给用户。
下例将实现一个ConfirmValidator,用于验证用户注册时两次密码必须输入一致:
1、创建一个空MVC项目
2、添加用户信息模型UserInfo.cs
public class UserInfo{
public string UserName { get; set; } public string Password { get; set; } public string ConfirmPassword { get; set; } public string Email { get; set; } }3、创建一个特性,用于指定与属性关联的另一个属性的名称
public class ConfirmValidatorAttribute : Attribute{
public String ConfirmPropertyName { get; set; } public ConfirmValidatorAttribute(string name) { ConfirmPropertyName = name; } }4、创建用于实现验证逻辑的ConfirmValidator类
public class ConfirmValidator : ModelValidator{
private string confirmPropertyName;
public ConfirmValidator(ModelMetadata metaData, ControllerContext context, string confirmProperty): base(metaData, context)
{ confirmPropertyName = confirmProperty; } public override IEnumerable<ModelValidationResult> Validate(object container) { if (container == null)yield break;
PropertyInfo pi = container.GetType().GetProperty(confirmPropertyName);
if (pi != null) { string confirmValue = (string)pi.GetValue(container, null); if ( !(Metadata.Model??String.Empty).Equals(confirmValue??String.Empty)) { yield return new ModelValidationResult() {Message = "两次输入不一致!"
};
} }else
{
throw new InvalidOperationException("属性" + confirmPropertyName + "不存在"); } } }5、创建用于产生ConfirmValidator的提供者类:ConfirmValidatorProvider
public class ConfirmValidatorProvider : AssociatedValidatorProvider { protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes) { foreach (ConfirmValidatorAttribute attr in attributes.OfType<ConfirmValidatorAttribute>()) { yield return new ConfirmValidator(metadata, context, attr.ConfirmPropertyName); } } }6、创建用于测试的控制器及视图
public class HomeController : Controller { public ActionResult Index() { return View(new UserInfo()); }[HttpPost]
public ActionResult Index(UserInfo ui) {return View(ui);
} }
<body><div><%using(Html.BeginForm()){ %><%= Html.ValidationSummary(true) %><%= Html.EditorFor(x=>x) %><input type="submit" value="提交" /><%} %></div></body>
7、修改UserInfo.cs,在ConfirmPassword属性上添加ConfirmValidator特性。
[ConfirmValidator("Password")]public string ConfirmPassword { get; set; }
8、在Global Application_Start中添加ConfirmValidatorProvider
ModelValidatorProviders.Providers.Add(new ConfirmValidatorProvider());
实现客户端验证:
1、修改ConfirmValidator类,添加GetClientValidationRules方法。
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules(){ModelClientValidationRule rule = new ModelClientValidationRule(){ErrorMessage = "两次输入不一致!",
ValidationType = "ConfirmValidator"
};
rule.ValidationParameters["ConfirmPropertyName"] = confirmPropertyName;
yield return rule;
}2、修改Index.aspx,添加对Ajax脚本的引用
<script type="text/javascript" src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js"></script><script type="text/javascript" src="http://www.cnblogs.com/Scripts/MicrosoftMvcValidation.js"></script>3、添加自定义验证脚本
<script type="text/javascript">Sys.Mvc.ValidatorRegistry.validators.ConfirmValidator = function (rule) {var propertyName = rule.ValidationParameters.ConfirmPropertyName;
return function (value, context) {var confirmValue = document.getElementsByName(propertyName)[0].value;
return (value == confirmValue);
}};</script>4、开启客户端验证功能
<%Html.EnableClientValidation(); %>5、将ConfirmPassword属性加入客户端验证
<% Html.ValidateFor(x => x.ConfirmPassword); %>修改后完整视图代码:
<body><div><%Html.EnableClientValidation(); %><%using(Html.BeginForm()){ %><%= Html.ValidationSummary(true) %><% Html.ValidateFor(x => x.ConfirmPassword); %><%= Html.EditorFor(x=>x) %><input type="submit" value="提交" /><%} %></div><script type="text/javascript" src="http://www.cnblogs.com/Scripts/MicrosoftAjax.js"></script><script type="text/javascript" src="http://www.cnblogs.com/Scripts/MicrosoftMvcValidation.js"></script><script type="text/javascript">Sys.Mvc.ValidatorRegistry.validators.ConfirmValidator = function (rule) {var propertyName = rule.ValidationParameters.ConfirmPropertyName;
return function (value, context) {
var confirmValue = document.getElementsByName(propertyName)[0].value;
return (value == confirmValue);
}};</script></body>