上次笔记说的是服务器端的验证,本次要说的是客户端验证。
对于数据注解特性来说,Asp.Net MVC框架的客户端验证是默认开启的,模型绑定器在对属性执行服务器端验证的时候也会触发客户端验证,比如默认的[Range]注解。客户端验证依赖于jQuery验证插件(jquery.validate),默认是在项目的Scripts文件夹下,如果想要实现客户端验证,那么需要一对脚本标签:
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
第一个script标签是一个精简的jQuery验证插件。验证插件实现了挂接到事件需要的所有逻辑(如提交和焦点事件),除此之外还需要执行客户端验证规则。该插件提供了丰富的默认验证规则集。
第二个script标签包括了用于jQuery验证的Microsoft非侵入式适配器。这段脚本中的代码用来获取ASP.NET MVC框架发出的元数据,并将这些元数据转换成jQuery验证能够理解的数据。元数据从哪里来呢?视图页中的强类型的辅助方法是关键:
@Html.TextBoxFor(m=>m.Number)
@Html.ValidationMessageFor(m=>m.Number)
TextBoxFor为基于元数据的模型构建输入元素,当TextBoxFor看到验证元数据(比如Number属性上的[Required]注解)时,它会将这些元数据放入到渲染的HTML中:
<input data-val="true" data-val-required="Number 字段是必需的。"id="Number" name="Number" type="text" value="" />
上述Script标签中,jquery.validate.unobtrusive脚本负责使用这个元数据(以data-val="true"开始)查找元素,并与jQuery验证插件结合起来执行元数据内的验证规则。jQuery验证可以运行每一个击键和焦点事件上的规则,给用户提供关于错误值的及时反馈。当错误出现时,验证插件也能阻止表单提交,这几意味着不需要再服务器上处理验证失败的请求。
下面将做个例子,来实现自定义客户端验证。
首先介绍下IClientValidatable接口。该接口定义了一个单一的方法:GetClientValidationRules。当ASP.NET MVC框架使用这个接口查找验证对象时,它会调用GetClientValidationRules方法来检索一个ModelClientValidationRule对象序列。这些对象携带有框架发送给客户端的元数据或者规则。下面是实现该接口的代码:
public class IsNumberAttribute : ValidationAttribute,IClientValidatable { public IsNumberAttribute():base("{0} is not a number.") { } /// <summary> /// 重写IsValid方法,实现服务器验证 /// </summary> /// <param name="value"></param> /// <param name="validationContext"></param> /// <returns></returns> protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (value != null) { if (!Char.IsNumber(value.ToString(), 0)) { var errorMessage = FormatErrorMessage(validationContext.DisplayName); //使用继承的FormatErrorMessage方法,确保我们正确的使用错误提示字符串 return new ValidationResult(errorMessage); } } return ValidationResult.Success; } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { //创建一个客户端模型验证规则的对象 //var rule = new ModelClientValidationRegexRule(FormatErrorMessage(metadata.GetDisplayName()), "^[0-9]*$"); var rule = new ModelClientValidationRule(); rule.ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()); rule.ValidationParameters.Add("pattern", "^[0-9]*$"); rule.ValidationType = "isnumber"; yield return rule; } }
上述代码的例子是接着服务器验证写的,一个验证是否是数字的功能。
其中,代码将错误消息放入规则的ErrorMessage属性中。这样可以使服务器端错误提示消息精确的匹配客户端错误提示消息。ValidationParameters集合用来存放客户端需要的参数,像例子中存放的是正则表达式,如果需要还可以继续想集合中添加参数。需要注意的是,参数的键,即名称是有意义的,它需要匹配在客户端脚本中看到的名称。ValidationType属性表示了客户端需要的一段Javascript代码,但这里必须使用小写,因为它序列化出来的值必须能够作为合法的HTML特性标识符使用。代码中注释的一句ModelClientValidationRegexRule方法,如果使用这句就不用写rule.ErrorMessage和rule.ValidationParameters两个属性了,因为它在客户端会自动序列化出同样的元数据。
下面是模型对象中属性使用注解的代码:
public class ValModel:IValidatableObject { [IsNumber(ErrorMessage = "您输入的不是数字。")] public string Number { get; set; } [IsNumber] public string checkNumber { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { if (!Char.IsNumber(checkNumber, 0)) { yield return new ValidationResult("输入的不是数字^_^", new[] { "checkNumber", "Number" }); } } }
这里的代码使用的是服务器验证的代码,且没有任何变化。页面视图代码如下:
@using (Html.BeginForm()) { @Html.AntiForgeryToken(); @Html.ValidationSummary(excludePropertyErrors: false) @Html.TextBoxFor(m=>m.Number) @Html.ValidationMessageFor(m=>m.Number) <br /> @Html.TextBoxFor(m=>m.checkNumber) @Html.ValidationMessageFor(m => m.checkNumber) <input type="submit" value="提交" /> }
到这里,运行看看,是否框架会根据GetClientValidationRules方法在客户端返回规则,并将信息其序列化为data-特性?如下:
<input data-val="true" data-val-isnumber="您输入的不是数字。" data-val-isnumber-pattern="^[0-9]*$" id="Number" name="Number" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="Number" data-valmsg-replace="true"></span> <br /> <input data-val="true" data-val-isnumber="checkNumber is not a number." data-val-isnumber-pattern="^[0-9]*$" id="checkNumber" name="checkNumber" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="checkNumber" data-valmsg-replace="true"></span>
如代码,果然序列化为data-特性。这句data-val-isnumber-pattern="^[0-9]*$" 对应的是 代码中rule.ValidationParameters.Add("pattern", "^[0-9]*$")序列化产生的,data-val-isnumber-pattern中的isnumber是由rule.ValidationType = "isnumber"这句决定的。
接口实现了,接下来就要自定义验证脚本代码
值得庆幸的是,在客户端没有必要编写代码来从data-特性中挖掘元数据值,但是,为了执行验证工作,需要以下两段脚本代码:
- 适配器:适配器和非侵入式MVC扩展一道识别需要的元数据。然后非侵入式扩展帮助从data-特性中检索值,并且还帮助把数据转换为jQuery验证能够理解的格式。
- 验证规则:在jQuery用语中被称作验证器。
这两段脚本都在同一个脚本文件中。下面是我的validate.js文件中的内容:
/// <reference path="jquery-1.5.1.js" /> /// <reference path="jquery.validate.js" /> /// <reference path="jquery.validate.unobtrusive.js" /> function ExeValidation() { $.validator.unobtrusive.adapters.addSingleVal("isnumber", "pattern"); $.validator.addMethod("isnumber", function (value, element, para) { if (value) { var re = new RegExp(para); if (!re.test(value)) return false; } return true; }); };
首先,最上面三条js的引用提供了所需要的所有智能感知功能。js代码ExeValidation方法中的第一句就是适配器。MVC框架的非侵入式验证扩展存储了jQuery.validator.unobtrusive.adapters对象中的所有适配器。这些适配器对象暴露了一个API,我们可以用来添加新适配器,如下表:
| 名称 | 描述 |
| addBool | 为“启用”或“禁止”的验证规则创建适配器 |
| addSingleVal | 为需要从元数据中检索唯一参数值的验证规则创建适配器 |
| addMinMax | 创建一个银蛇到验证规则集的适配器--一个用来检查最小值,另一个用来检查最大值。这两个中至少有一个要依靠得到的数据运行。 |
| Add | 创建一个不适合前面类别的适配器。 |
对于本例子来说,只需要使用addSingleVal就行了。
编写适配器:$.validator.unobtrusive.adapters.addSingleVal("isnumber", "pattern");
第一个参数是适配器的名称,它必须与服务器端设置的ValidationType值匹配。第二个参数是要从元数据中检索的单一参数的名称(即例子中包含正则表达式的参数)。注意,这里参数名称没有data-前缀;在服务器上它匹配放入ValidationParameters集合中的参数名称。
适配器相对来说比较简单。适配器的主要目标是识别非侵入式扩展要定位的元数据。有了适配器,就可以编写验证器了(验证规则)。
所以的验证器都在jQuery.validator对象中。与adapters对象类似,validator对象也一个API函数,可以用来添加新的验证器,该函数的名称是addMethod:
$.validator.addMethod("isnumber", function (value, element, para) {
if (value) {
var re = new RegExp(para);
if (!re.test(value))
return false;
}
return true;
});
该方法中有两个参数:
- 验证器的名称:默认下,验证器的名称要匹配适配器的名称,而适配器的名称又要匹配服务器上的ValidationType的属性值。
- 函数:当验证发生时调用。
函数接收了单个参数,并在验证成功时返回true,失败返回false:
- 函数的第一个参数包含输入的值。
- 第二个参数是输入的元素,其中包含了要验证的值(在value本身没有听过足够的信息的情况下使用)。
- 第三个参数包含一个数组中的所有验证参数。在本例子中就只有一个参数(即正则表达式)。
准备完了这些,下面设置下视图:
<div> @using (Html.BeginForm()) { @Html.AntiForgeryToken(); @Html.ValidationSummary(excludePropertyErrors: false) @Html.TextBoxFor(m=>m.Number) @Html.ValidationMessageFor(m=>m.Number) <br /> @Html.TextBoxFor(m=>m.checkNumber) @Html.ValidationMessageFor(m => m.checkNumber) <input type="submit" value="提交" /> } </div> @section scripts { <script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/validate.js")" type="text/javascript"></script> ExeValidation(); }
这里validate.js的引用要确保在验证脚本之后。运行结果如下:

客户端验证不需要点提交按钮,框架会自动调用事件来执行自定义的验证脚本代码,在不通过的情况下,点击提交按钮网站不会刷新,即是不会回发的。
到此客户端验证就到这里了。
浙公网安备 33010602011771号