博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

自定义注解--客户端验证

Posted on 2013-04-18 14:55  shyleaf  阅读(414)  评论(0)    收藏  举报

  上次笔记说的是服务器端的验证,本次要说的是客户端验证。

   对于数据注解特性来说,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的引用要确保在验证脚本之后。运行结果如下:

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

到此客户端验证就到这里了。