在模型中添加验证规则

概述

上篇文章中介绍了添加数据,在提交表单的数据的时候,我们需要对数据的合法性进行校验,Asp.net MVC5中,提供一种方便的验证方式。本文介绍如何在我们的Student模型中添加一些验证规则,同时确认当用户使用我们的应用程序创建或编辑学生信息时将使用这些验证规则对用户输入的信息进行检查。

DRY原则

在ASP.NET MVC中,有一条作为核心的原则,就是DRY(“Don’t Repeat Yourself,中文意思为:不要让开发者重复做同样的事情)原则。ASP.NET MVC提倡让开发者“一处定义、处处可用”。这样可以减少开发者的代码编写量,同时也更加便于代码的维护。

在模型中添加验证规则

首先,让我们在Student类中追加一些验证规则。

打开Student.cs文件,在文件的头部追加一条引用System.ComponentModel.DataAnnotations命名空间的using语句,代码如下所示。

1   using System.ComponentModel.DataAnnotations;

这个System.ComponentModel.DataAnnotations命名空间是.NET Framework中的一个命名空间。它提供了很多内建的验证规则,你可以对任何类或属性显式指定这些验证规则。

现在让我们来修改Student类,增加一些内建的Required(必须输入),StringLength(输入字符长度)与Range(输入范围)验证规则,当然,我们也可以自定义我们自己的验证规则,之后会说明如何创建自定义验证规则。

//------------------------------------------------------------------------------
// <auto-generated>
//     此代码已从模板生成。
//
//     手动更改此文件可能导致应用程序出现意外的行为。
//     如果重新生成代码,将覆盖对此文件的手动更改。
// </auto-generated>
//------------------------------------------------------------------------------

namespace Wolfy.FirstMVCProject.Models
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;

    public partial class Student
    {
        public Student()
        {
            this.Score = new HashSet<Score>();
        }

        public int stuId { get; set; }
        [Required(ErrorMessage = "必须输入标题")]
        public string stuName { get; set; }

        public string stuSex { get; set; }
        public System.DateTime stuBirthdate { get; set; }
        public System.DateTime stuStudydate { get; set; }
        [StringLength(4, ErrorMessage = "只能输入4个字符")]
        public string stuAddress { get; set; }
        [Required(ErrorMessage = "必须输入标题")]
        //正则验证
        [RegularExpression(@"^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$", ErrorMessage = "邮箱格式不正确")]
        public string stuEmail { get; set; }
        [RegularExpression(@"\d{11}", ErrorMessage = "邮箱格式不正确")]
        public string stuPhone { get; set; }
        public Nullable<bool> stuIsDel { get; set; }
        public Nullable<System.DateTime> stuInputtime { get; set; }
        public int classId { get; set; }

        public virtual Course Course { get; set; }
        public virtual ICollection<Score> Score { get; set; }
    }
}

上述这些验证属性指定了我们想要强加给模型中各属性的验证规则。Required属性表示必须要指定一个属性值,在上例中,一个有效的学生信息必须含有标题,地址,电话,邮箱。Range属性表示属性值必须在一段范围之间。StringLength属性表示一个字符串属性的最大长度或最短长度。

Action中的代码

[HttpPost]
public ActionResult Create(Student student)
{
    //ModelState.IsValid校验客户端数据是否全部符合验证规则
    if (ModelState.IsValid)
    {
        //获取dropdownlist选中的value值
        string strClassID = Request.Form["class"];
        int intId = Convert.ToInt32(strClassID);
        var course = from c in entity.Course
                     where c.classId == intId
                     select c;
        //处理外键关系
        student.Course = course.FirstOrDefault();
        entity.Student.Add(student);
        entity.SaveChanges();
        return RedirectToAction("Index");

    }
    else
    {
        //在不符合验证规则的时候,得重新绑定DropDownList数据源。
        var courses = from s in entity.Course
                      select s;
        List<SelectListItem> items = new List<SelectListItem>();
        foreach (var item in courses)
        {
            SelectListItem selectListItem = new SelectListItem() { Text = item.className, Value = item.classId.ToString() };
            items.Add(selectListItem);
        }
        ViewData["class"] = items;
        return View(student);
    }
}

运行效果

自定义验证规则

如果上面的验证规则,不能满足需要,可以自己定义验证规则,首先看一下,Required是如何实现的,咱们就模仿Required来实现一个自己的验证规则。

namespace System.ComponentModel.DataAnnotations
{
    // 摘要:
    //     指定需要数据字段值。
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
    public class RequiredAttribute : ValidationAttribute
    {
        // 摘要:
        //     初始化 System.ComponentModel.DataAnnotations.RequiredAttribute 类的新实例。
        public RequiredAttribute();

        // 摘要:
        //     获取或设置一个值,该值指示是否允许空字符串。
        //
        // 返回结果:
        //     如果允许空字符串,则为 true;否则为 false。 默认值为 false。
        public bool AllowEmptyStrings { get; set; }

        // 摘要:
        //     检查必填数据字段的值是否不为空。
        //
        // 参数:
        //   value:
        //     要验证的数据字段值。
        //
        // 返回结果:
        //     如果验证成功,则为 true;否则为 false。
        //
        // 异常:
        //   System.ComponentModel.DataAnnotations.ValidationException:
        //     数据字段值为 null。
        public override bool IsValid(object value);
    }
}
// 摘要:
   //     作为所有验证属性的基类。
   //
   // 异常:
   //   System.ComponentModel.DataAnnotations.ValidationException:
   //     在设置非本地化 System.ComponentModel.DataAnnotations.ValidationAttribute.ErrorMessage
   //     属性错误消息的同时,本地化错误消息的 System.ComponentModel.DataAnnotations.ValidationAttribute.ErrorMessageResourceType
   //     和 System.ComponentModel.DataAnnotations.ValidationAttribute.ErrorMessageResourceName
   //     属性也被设置。
   public abstract class ValidationAttribute : Attribute
   {
      //其他代码
    }

所以,验证规则特性必须集成ValidationAttribute类,当然也可以继承该类的子类。(注意:对于特性约定以Attribute结尾)。 我们就自定义一个验证类,实现默认值约束规则DefaultsAttribute,而实现最简单的方式就是自定义正则表达式的规则。

定义的验证类的代码如下:

public class DefaultsAttribute : RegularExpressionAttribute
{
    public DefaultsAttribute()
        : base("[mf]")
    {

    }
    public override string FormatErrorMessage(string name)
    {
        return "性别只能输入m(男)或者f(女)";
    }
}

为Student类中的stuSex加上特性。

[Defaults]
public string stuSex { get; set; }

然后测试一下,看看实现效果

在Create视图(追加学生信息视图)与Create方法内部是如何实现验证的?

该方法中的ModelState.IsValid属性用来判断是否提交的学生信息数据中包含有任何没有通过数据验证的无效数据。如果存在无效数据,Create方法重新返回追加学生信息视图。如果数据全部有效,则将该条数据保存到数据库中。

我们之前创建的使用支架模板的Create.cshtml视图模板中的代码显示如下,在首次打开追加学生信息视图与数据没有通过验证时,Create方法中返回的视图都是使用的这个视图模板。

@model Wolfy.FirstMVCProject.Models.Student

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Create</title>
</head>
<body>
    @using (Html.BeginForm("Create","Student",FormMethod.Post))
    {
        @Html.AntiForgeryToken()

        <div class="form-horizontal">
            <h4>Student</h4>
            <hr />
            @Html.ValidationSummary(true)

            <div class="form-group">
                @Html.LabelFor(model => model.stuName, new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.stuName)
                    @Html.ValidationMessageFor(model => model.stuName)
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.stuSex, new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.stuSex)
                    @Html.ValidationMessageFor(model => model.stuSex)
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.stuBirthdate, new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.stuBirthdate)
                    @Html.ValidationMessageFor(model => model.stuBirthdate)
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.stuStudydate, new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.stuStudydate)
                    @Html.ValidationMessageFor(model => model.stuStudydate)
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.stuAddress, new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.stuAddress)
                    @Html.ValidationMessageFor(model => model.stuAddress)
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.stuEmail, new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.stuEmail)
                    @Html.ValidationMessageFor(model => model.stuEmail)
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.stuPhone, new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.stuPhone)
                    @Html.ValidationMessageFor(model => model.stuPhone)
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.stuIsDel, new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.stuIsDel)
                    @Html.ValidationMessageFor(model => model.stuIsDel)
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.stuInputtime, new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.stuInputtime)
                    @Html.ValidationMessageFor(model => model.stuInputtime)
                </div>
            </div>

            <div class="form-group">
                @Html.LabelFor(model => model.Course.classId, "stuClass", new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.DropDownList("class", String.Empty)
                    @Html.ValidationMessageFor(model => model.classId)
                </div>
            </div>

            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" value="Create" class="btn btn-default" />
                </div>
            </div>
        </div>
    }

    <div>
        @Html.ActionLink("Back to List", "Index")
    </div>
</body>
</html>

请注意在这段代码中使用了许多Html.EditorFor帮助器来为Student类的每个属性输出一个输入文本框。在每个Html.EditorFor帮助器之后紧跟着一个Html.ValidationMessageFor帮助器。这两个帮助器将与从控制器传入的模型类的对象实例(在本示例中为Student对象的一个实例)结合起来,自动寻找指定给模型的各个验证属性,然后显示对应的验证错误信息。

这种验证体制的好处是在于控制器和Create视图(追加学生信息视图)事先都即不知道实际指定的验证规则,也不知道将会显示什么验证错误信息。验证规则和错误信息只在Student类中被指定。

如果我们之后想要改变验证规则,我们也只要在一处地方进行改变就可以了。我们不用担心整个应用程序中存在验证规则不统一的问题,所有的验证规则都可以集中在一处地方进行指定,然后在整个应用程序中使用这些验证规则。这将使我们的代码更加清晰明确,更加具有可读性、可维护性与可移植性。这将意味着我们的代码是真正符合DRY原则(一处指定,到处可用)的。

伙伴类

这个demo中,我们使用的是EF的方式操作数据库的,如果数据库中表的字段有变化时,我们就要更新模型,如图所示:

如果从数据库中更新模型,就会把上面添加的规则给替换掉,那么之前写的验证规则就白费了,那么有没有解决办法呢?办法是有的,就是将要说的伙伴类。伙伴类的定义如下,

public class StudentValidate
{
    public int stuId { get; set; }
    [Required(ErrorMessage="姓名是必须的")]
    public string stuName { get; set; }
    public string stuSex { get; set; }
    public System.DateTime stuBirthdate { get; set; }
    public System.DateTime stuStudydate { get; set; }
    public string stuAddress { get; set; }
    public string stuEmail { get; set; }
    public string stuPhone { get; set; }
    public Nullable<bool> stuIsDel { get; set; }
    public Nullable<System.DateTime> stuInputtime { get; set; }
    public int classId { get; set; }
}
//指定要与数据模型类关联的元数据类。
[MetadataType(typeof(StudentValidate))]
public partial class Student
{

}
}

你创建另外一个类,包含你的验证特性和元数据,然后通过将 “MetadataType”特性施加到一个与工具生成的类一起编译的partial类上,将其与由设计器生成的类连接起来。例如,如果我们想要将我们前面用到的验证规则施加到由LINQ to SQL 或 ADO.NET EF设计器维护的Student类上,我们可以更新我们的验证代码,使其存在于一个单独的“StudentValidate”类上。

测试,如上面代码所示,为了简单测试,我们只验证姓名是必须的字段,其他的验证规则跟上面一样,就不再一一添加了,感兴趣的可以自己测试一下。

这种方式可以避免因为从数据库更新模型而造成之前的model别修改。

总结

本文介绍了表单验证的方式,当然,你也可以使用客户端的校验方式,表单验证插件也挺多的,这里就不再赘述了。另外介绍了自定义的验证规则,在验证方式不符合自己要求的时候,可以自定义一个验证规则,当然你可以直接使用正则表达式的验证规则,但是如果这个验证规则经常使用,那么将它定义为一个规则特性,像使用Required一样去使用,是不是更方便?在文章最后,介绍了一种由于更新model造成原来的model改变,修改了原来的验证规则,可以通过伙伴类来保证原来的验证规则不变,又能更新model。