65.9K
CodeProject 正在变化。 阅读更多。
Home

在 MVC 3 中创建自定义验证属性

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (14投票s)

2011年12月16日

CPOL

5分钟阅读

viewsIcon

178539

downloadIcon

2843

本文解释了如何在 MVC 3 中创建自定义验证属性,用于在服务器端和客户端验证数据。

引言

本文将演示如何在 MVC 3 应用程序中创建自定义验证属性。MVC 3 提供了许多现成的验证属性,但有时我们需要特定类型的验证,而这种验证可能依赖于其他属性。在本文中,我将解释如何创建自己的自定义验证属性,以及如何使其在客户端也能够工作。在 MVC 应用程序中,要验证数据,只需在模型类中指定的属性上添加一个属性,并在更新信息时调用“TryUpdateModel”方法或检查“ModelState.IsValid”属性即可。但这只是在服务器端进行验证,那么如何才能在客户端进行验证呢?需要为视图添加验证控件,并在使用验证的视图中添加对验证 jQuery 文件“jquery.validate.min.js”和“jquery.validate.unobtrusive.min.js”的引用。

在 MVC 3 应用程序中创建自定义验证是一个 4 步过程

  1. 创建一个继承“ValidationAttribute”并实现“IClientValidatable”接口的类。“IClientValidatable”接口的实现对于在客户端启用验证是必需的。
  2. 重写“IsValid”方法并在其中添加自定义验证逻辑。
  3. 实现“GetClientValidationRules”方法。您可以在此方法中添加客户端验证所需的自定义参数。
  4. 创建一个 JavaScript 文件,并使用“jQuery.validator.unobtrusive.add”和“jQuery.validator.addMethod”方法在此注册您的客户端方法。在下一节的详细代码解释中,我们将深入探讨这一点。

背景

在创建示例 MVC 应用程序时,我遇到一个情况,需要对一组复选框进行验证。我需要的验证很简单,就是至少有一个复选框必须被选中。虽然这可以通过简单的脚本并在按钮点击时调用来实现,但我认为为什么不为此编写一个自定义验证属性呢?这样做的原因是我可以学习一项新功能,代码会保持整洁,并且代码可以重用。

代码

使用 Razor 视图引擎创建一个 MVC 3 应用程序(您可以根据自己的喜好使用 aspx 视图引擎)。要在 Visual Studio 中创建 MVC 3 应用程序,您需要安装 Visual Studio 的 Service Pack 1 和 MVC3 wpi,您可以在 Microsoft 网站上下载。在此应用程序中,我创建了一个具有某些属性的 EmployeeModel 和一个创建方法,一个 EmployeeController 包含两个操作,一个视图包含显示和接受员工属性输入值的控件。为了验证,我创建了一个名为“Validations”的单独文件夹,并在其中添加了一个文件“MvcCustomValidation.cs”。现在让我们看看要在“MvcCustomValidation.cs”文件中编写什么才能使我们的自定义验证器工作。

以下是“MvcCustomValidation.cs”文件中编写的代码

using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using System.Collections.Generic;
namespace MvcCustomValidation.Validations
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
    public class MyCustomValidation : ValidationAttribute, IClientValidatable
    {
        public string CommaSeperatedProperties { get; private set; }
        public MyCustomValidation(string commaSeperatedProperties)
            : base("Please select an option.")
        {
            if (!string.IsNullOrEmpty(commaSeperatedProperties))
                CommaSeperatedProperties = commaSeperatedProperties;
        }
        protected override ValidationResult IsValid
        (object value, ValidationContext validationContext)
        {
            if (value != null)
            {
                string[] strProperties = 
        CommaSeperatedProperties.Split(new char[] { ',' });
                bool bIsAnyChecked = false;
                if (Convert.ToBoolean(value))
                {
                    bIsAnyChecked = true;
                }
                else
                {
                    foreach (string strProperty in strProperties)
                    {
                        var curProperty = validationContext.ObjectInstance.GetType().
                    GetProperty(strProperty);
                        var curPropertyValue = curProperty.GetValue
                (validationContext.ObjectInstance, null);
                        if (Convert.ToBoolean(curPropertyValue))
                        {
                            bIsAnyChecked = true;
                            break;
                        }
                    }
                }
                if (!bIsAnyChecked)
                    return new ValidationResult("Please select an option.");
            }
            return ValidationResult.Success;
        }
        public IEnumerable<ModelClientValidationRule> 
        GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            return new[] { new ModelClientValidationSelectOneRule
            (FormatErrorMessage(metadata.DisplayName), 
        CommaSeperatedProperties.Split(new char[] { ',' })) };
        }
        public class ModelClientValidationSelectOneRule : ModelClientValidationRule
        {
            public ModelClientValidationSelectOneRule
        (string errorMessage, string[] strProperties)
            {
                ErrorMessage = errorMessage;
                ValidationType = "mycustomvalidation";
                for (int intIndex = 0; intIndex < strProperties.Length; intIndex++)
                {
                    ValidationParameters.Add("otherproperty" + 
                    intIndex.ToString(), strProperties[intIndex]);
                }
            }
        }
    }
} 

上面,我创建了一个继承自 ValidationAttribute 类并实现 IClientValidatable 接口的类。我想在验证此属性的值时访问一些其他属性,因此我将这些属性名称作为逗号分隔的 string 传递到构造函数中。这些属性名称用于在 IsValid 方法中检索属性值,在该方法中,我使用这些属性名称从 ValidationContext 对象参数中检索属性值。在这里,我实现了我的自定义验证逻辑,其中我检查要执行验证的属性的值以及作为参数传递的也需要检查以进行验证的属性。

实现 IsValid 方法后,我实现了 GetClientValidationRules 方法,这对于客户端验证是必需的。在此方法中,我设置了一些属性,例如 ErrorMessageValidationTypeValidationParameters。在 ValidationParameters 中,我使用键 otherproperty0otherproperty1 等添加属性名称。稍后,当我们在 jQuery 中注册方法时,必须使用相同的名称。

现在我们完成了 ValidationAttribute 的编码。让我们继续进行 jQuery,以便我们也可以在客户端启用验证。看看我添加到Script文件夹的 JQuery 文件“MyCustomValidation.js

jQuery.validator.unobtrusive.adapters.add
    ("mycustomvalidation", ['otherproperty0', 'otherproperty1'],
    function (options) {
        options.rules['mycustomvalidation'] = { other: options.params.other,
            otherproperty0: options.params.otherproperty0,
            otherproperty1: options.params.otherproperty1
        };
        options.messages['mycustomvalidation'] = options.message;
    }
);
jQuery.validator.addMethod("mycustomvalidation", function (value, element, params) {
    var retVal = false;
    if ($(element)) {
        retVal = $(element).attr("checked");
    }
    if (retVal == true) {
        return retVal;
    } 
    if (params.otherproperty0) {
        if ($('#' + params.otherproperty0)) {
            retVal = $('#' + params.otherproperty0).attr("checked");
        }
    }
    if (retVal == true) {
        return retVal;
    }
    if (params.otherproperty1) {
        if ($('#' + params.otherproperty1)) {
            retVal = $('#' + params.otherproperty1).attr("checked");
        }
    }
    if (retVal == true) {
        return retVal;
    }
    return false;
}); 

在“MyCustomValidation.js”文件中,我们只注册了 2 个方法。我们将一个方法添加到“jQuery.validator.unobtrusive.adapters”。这里我们有验证名称和其他属性名称。这里需要注意的一点是,其他属性名称必须与我们在服务器端实现 GetClientValidationRule 方法时指定的名称相同。我们将在此方法中为我们的自定义验证设置消息属性和验证规则。第二个方法我们将添加到验证器“jQuery.validator.addMethod”,在此方法中,我们将通过从 params 获取其他属性来执行我们的自定义验证逻辑。耶!我们已经完成了服务器端和客户端的验证属性,现在让我们看看如何使用它。查看Model文件夹中的EmployeeModel.cs文件。

using System.Web.Mvc;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using MvcCustomValidation.Validations;
namespace MvcCustomValidation.Models
{
    public class EmployeeModel
    {
        [Required]
        [StringLength(50)]
        [Display(Name = "Employee Name")]
        public string EmpName { get; set; }
        [Required]
        [StringLength(150)]
        [Display(Name = "Email Id")]
        public string Email { get; set; }
        [Required]
        [StringLength(15)]
        [Display(Name = "Contact No.")]
        public string Number { get; set; }
        [Required]
        [Range(18, 150)]
        public int Age { get; set; }
        [Display(Name = "Email")]
        public bool IsEmail { get; set; }
        [Display(Name = "SMS")]
        [MyCustomValidation("IsEmail,IsAlert")]
        public bool IsSMS { get; set; }
        [Display(Name = "Alert")]
        public bool IsAlert { get; set; }
    }
    public class Employee
    {
        public Employee()
        {
           
        }
        public List<EmployeeModel> _empList = new List<EmployeeModel>();
        public void Create(EmployeeModel umToUpdate)
        {
            foreach (EmployeeModel um in _empList)
            {
                if (um.EmpName == umToUpdate.EmpName)
                {
                    throw new System.InvalidOperationException
            ("Duplicate username: " + um.EmpName);
                }
            }
            _empList.Add(umToUpdate);
        }
    }
} 

查看 EmployeeModel 类中的属性。这里我有一个需求,属性 IsEmailIsAlertIsSMS 将显示为与单个标题“通知模式”相对应的 checkbox,如果用户未在 UI 上选择任何 checkbox,系统应显示验证消息。我已经将我们的自定义验证属性添加到了 IsSMS 属性上,并将属性名“IsAlert”和“IsSMS”作为逗号分隔的 string 传递给了它。在模型类中,我们只需将自定义属性添加到特定属性并向其传递所需的参数。

现在看看视图。我们的视图位置是“Views/Employees/Create.cshtml”。以下是我们需要在视图中编写的 HTML 代码。

@model MvcCustomValidation.Models.EmployeeModel
@{
    ViewBag.Title = "Create";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Create</h2>
<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/MyCustomValidation.js")" 
    type="text/javascript"></script>
@using (Html.BeginForm())
{    
    <fieldset>
        <legend>EmployeeModel</legend>
        <table style="width: 100%; padding: 1; border: 0;">
            <tr>
                <td class="editor-label" style="border:0;">
                    @Html.LabelFor(model => model.EmpName)
                </td>
                <td class="editor-field" style="border:0;">
                    @Html.EditorFor(model => model.EmpName)
                    @Html.ValidationMessageFor(model => model.EmpName)
                </td>
            </tr>
            <tr>
                <td class="editor-label" style="border:0;">
                    @Html.LabelFor(model => model.Email)
                </td>
                <td class="editor-field" style="border:0;">
                    @Html.EditorFor(model => model.Email)
                    @Html.ValidationMessageFor(model => model.Email)
                </td>
            </tr>
            <tr>
                <td class="editor-label" style="border:0;">
                    @Html.LabelFor(model => model.Number)
                </td>
                <td class="editor-field" style="border:0;">
                    @Html.EditorFor(model => model.Number)
                    @Html.ValidationMessageFor(model => model.Number)
                </td>
            </tr>
            <tr>
                <td class="editor-label" style="border:0;">
                    @Html.LabelFor(model => model.Age)
                </td>
                <td class="editor-field" style="border:0;">
                    @Html.EditorFor(model => model.Age)
                    @Html.ValidationMessageFor(model => model.Age)
                </td>
            </tr>
            <tr>
                <td class="editor-label" style="border:0;">
                    @Html.Label("Notification Mode/s")
                </td>
                <td class="editor-field" style="border:0;">
                    @Html.CheckBoxFor(model => model.IsAlert) 
            @Html.LabelFor(model => model.IsAlert) @Html.Raw("     ")
                    @Html.CheckBoxFor(model => model.IsEmail) 
            @Html.LabelFor(model => model.IsEmail) @Html.Raw("     ")
                    @Html.CheckBoxFor(model => model.IsSMS) 
            @Html.LabelFor(model => model.IsSMS)
                </td>
            </tr>
            <tr>
                <td class="editor-label" style="border:0;">
                </td>
                <td class="editor-field" style="border:0;">
                    @Html.ValidationMessageFor(model => model.IsSMS)
                </td>
            </tr>
            <tr>
                <td colspan="2" style="border:0;">
                    <input type="submit" value="Save" />
                </td>
            </tr>
        </table>
    </fieldset>
}

查看上面的 HTML 代码。我为属性 IsAlertIsEmailIsSMS 渲染了 checkbox,并针对通知模式,并仅为 IsSMS 属性添加了一个验证控件,如下所示:

@Html.ValidationMessageFor(model => model.IsSMS) 

如果您想测试此验证如何在服务器端工作,只需从视图中删除脚本引用或在 web.config 中禁用客户端验证,然后运行应用程序。

© . All rights reserved.