3.2.3 设置模型验证规则

      模型验证是确保应用程序所接收的数据适合于绑定到模型,并且在不合适时给用户提供有用的信息,以帮助他们修正其问题的过程,防止在存储库中放置劣质或无意义的数据,并引起问题。该过程的第一部分,检查接收的数据是保持域模型完整性的方式之一。通过拒绝域环境中无意义的数据,可以防止应用程序中出现一些奇怪和意想不到的情况。第二部分,帮助用户修正问题,同样是重要的。如果不给用户提供一些与应用程序进行交互所需要的信息和反馈,他们会感到受挫和困惑,用户可能会因此直接停用这个应用程序。MVC框架对模型验证提供了广泛的支持。

      从全局来看,发现逻辑仅是整个验证的很小的一部分。验证首先需要管理用户友好(本地化)的与验证逻辑相关的错误提示消息;当验证失败时,在把这些错误提示消息呈现给用户界面上,当然还要向用户提供从验证失败中恢复的机制。本节将演示如何使用这些基本特性。

一、控制器中验证模型

      验证一个模型最直接的方式,是在动作方法中实现。

[HttpPost]
public ViewResult MakeBooking(Appointment appt){
    if ( String.IsNullOrEmpty(appt.ClientName)){
        ModelState.AddModelError("ClientName","Please enter your name");
    }
  if( ModelState.IsValidField("Date") && DateTime.Now>appt.Date){
    ModelState.AddModelError("Date","Please enter a date in the future");
  }
  
  if ( ModelState.IsValid){
    //插入appt数据到数据库
    ......
}

return View(); }

      ModelState属性是控制器从它的基类集成而来的。

      ModelState.AddModelError方法指定有问题的属性名(ClientName),和一条应该显示给用户的消息(“Please enter your name”)。

      ModelState.IsValidField方法检查模型绑定器是否能够对一个属性赋值。对于Date属性就是这么做的,以确保模型绑定器能够解析用户所递交的值。

      ModelState.IsValid属性检查是否有错误发生。如果在检查期间调用ModelState.AddModelError方法,或在创建Appointment对象时,模型绑定器遇到了问题,该方法会返回true。

二、用元数据指定验证规则 (模型中验证)   

        ASP.NET MVC中的一个设计原则是DRY(“Don't Repeat Yourself”)。相同功能或行为的代码只写一遍,然后在应用程序的任何地方都可以引用。这样减少了代码数量,降低使代码出错可能性,并且更容易维护。ASP.NET MVC和Entity Framework Code First中的验证规则设置,就是DRY的一个很好的实践。你可以在模型中通过添加数据注释来进行验证规则声明,然后这个验证规则就可以在应用程序的各个地方使用。元数据模型验证是在模型绑定时检查从HTTP请求接收的数据是否合规以保证数据的有效性,在收到无效数据时给出提示帮助用户纠正错误的数据。

1. 声明主键

      要想在Entity Framework声明主键,最简单的方式就是不要声明,直接把属性名称设置为Id(或是类名+“Id”,或是ID),并将该属性指派为int类型即可。EF Code First会自动识别出这个字段就是表格里的主键,并且会加上自动编号的识别规格设置。

     当希望使用其他域名当作主键时,就可能遇到一些麻烦。

      这个错误的产生原因在于,任何EF里的模型,都被要求一定有主键,所以当EF无法识别出哪个字段是主键时,就会引发这个异常。

      解决此问题的方法就是在No属性(Property)上加上一个key属性(Attriute),引用System.ComponentModel.DataAnnotations命名空间。

 
using System;
using System.ComponentModel.DataAnnotations;

namespace MvcGuestbook.Models
{
    public class Guestbook
    {
        [Key]
        public int No { get; set; }
        public string Name { get; set; }
public string Email { get; set; }
        public string Message { get; set; }
        public DateTime CreatedOn { get; set; } } }
 

2. 声明必填字段

      Required 注释告诉 EF 某一个特定属性是必需的。在 Title 属性中添加 Required 将强制 EF(和 MVC)确保该属性中包含数据。

[Required]
public string Title { get; set; }

      MVC 应用程序无需添加其他代码或更改标记,就能执行客户端验证,甚至还能使用属性和注释名称动态生成消息。

      

      Required特性还将使被映射的属性不可为空来影响生成的数据库。请注意,Title 字段已经更改为“not null”。

      Required属性对值类型来说不必要,如Datetime、Double、Float,因为值类型不能分配 null 值。但对于String等引用类型的属性来说,就可以根据情况进行设置。

    还可以自定义Required验证出错时的提示。

[Required(ErrorMessage="wrong name")]
public string Name { get; set; }
}

3. 声明允许NULL字段

      声明为DateTime的属性,在数据库表格里的字段设置为NOT NULL。如果需要改变允许为空字段,可以加上一个问号,不需要引用任何命名空间。

using System;
using System.ComponentModel.DataAnnotations;

namespace MvcGuestbook.Models
{
    public class Guestbook
    {
        [Key]
        public int No { get; set; }

        [Required]
        public string Name { get; set; }

        public string Emai { get; set; }

        [Required]
        public string Message { get; set; }

        public DateTime? CreatedOn { get; set; }
        public Nullable<DateTime> RepliedOn { get; set; }
} }

4. 声明字符串长度

(1)MaxLength和MinLength     

      我们也经常会在数据库中限定特定字段的字符串长度,以方便日后创建字段索引,可以使用MaxLength属性,引用System.ComponentModel.DataAnnotations。

[MaxLength(10), MinLength(5)]
public string BloggerName { get; set; }

      也可以在注释中指定ErrorMessage。

[MaxLength(10,  ErrorMessage="BloggerName 必须在 10 个字符以下"), MinLength(5)]
public string BloggerName { get; set; }

(2)StringLength      

[StringLength(50, MinimumLength=1)]
public string LastName { get; set; }

5.Compare

      两个字段值必须一样,比如可以在要求用户重复输入密码时用。

[DataType(DataType.Password)]
public String Password { get; set; }
[DataType(DataType.Password)]

[Compare("Password",ErrorMessage="密码要一致")]
public String RptPassword { get; set; }

6.Range

      一个数字(或实现了IComparable的类型)的值必须在指定范围内。

[Range(35,44)]
public int Age { get; set; }

    小数的情况:

[Range(typeof(decimal), “0.00”, “49.99”)]
public decimal Price { get; set; }

 7.声明字段默认值

      在做数据库规划时,通常会规划一些系统字段,也就是由数据库本身自行指定默认值到这个字段上,创建信息的“创建时间”字段就会常常这样设计。如果CreatedOn字段希望能有默认值,且让.NET程序在新增信息到数据库时不用指定其值的话,那么你应该在该属性上加一个DatabaseGenerated属性,并出传入DatabaseGeneratedOption.Computed参数到DatabaseGenerated属性中,引用System.ComponentModel.DataAnnotations.Schema命名空间。

 

      数据库创建完成后的架构如下图。

      图中CreatedOn字段并没有被指派默认值上去,DatabaseGenerated特性,让EF不再追踪这个属性的任何对象变化,你可以在数据库中手动加上CreatedOn字段的默认值。

     更改完字段后,记得单击“更新”按钮,才会将本次的更新反映到数据库中。

     如果们在.net代码中,将guestbook兑现的CreatedOn属性设置日期为2012/02/02这个时间点。

      但事实上CreatedOn字段被写入的信息,仍然是由数据库所指定的默认值。

  • DatabaeGeneratedOption.Computed,计算列
  • DatabaseGeneratedOption.Identity,标识自动加1
  • DatabaseGeneratedOption.None,没有标识,不自动加1

8. 声明特定属性不是数据库中的字段

      在EF Code First框架里,只要数据模型中出现公开属性,默认就会在数据库中创建一个对应的字段,但如果在数据模型中的属性,是一个动态计算的属性,我们并不想在数据库中新增对应的字段时,该怎么办?可以加上NotMapped属性(Attribute),引用System.ComponentModel.DataAnotations.Schema。

 
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcGuestbook.Models
{
    public class Guestbook
    {
        [Key]
        public int No { get; set; }

        [Required]
        [MaxLength(5)]
        public string Name { get; set; }

        [MaxLength(200)]
        public string Emai { get; set; }

        [Required]
        public string Message { get; set; }

        [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
        public DateTime? CreatedOn { get; set; }

        [NotMapped]
        public string FamilyName
        {
            get
            {
                return this.Name.Substring(0, 1);
            }
            set
            {
                this.Name = value.Substring(0, 1) + this.Name.Substring(1);
            }
        }
    }
}

9.正则验证属性

[RegularExpression(@"^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$", ErrorMessage = "邮箱格式不正确")]
public string stuEmail { get; set; }
[RegularExpression(@”[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+/.[A-Za-z]{2,4}”)]
public string Email { get; set; } [RegularExpression(
@"\d{11}", ErrorMessage = "手机格式不正确")] public string stuPhone { get; set}

      如果不熟悉正则表达式的编写,可以百度搜索相应的正则表达式。推荐一个在线检测正则表达式的网站,一个正则表达式学习网站

      常见正则验证规则如下:

  • Email,[RegularExpression(@"(\w)+(\.\w+)*@(\w)+((\.\w+)+)", ErrorMessage = "{0}格式不正确")]
  • 手机,[RegularExpression("^[1][3578][0-9]{9}$",ErrorMessage="手机号码输入不正确")]
  • 密码, [RegularExpression("^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$", ErrorMessage = "密码必须包含数字和字母,且长度在6-20位之间")]
  • QQ,[RegularExpression("[1-9][0-9]{4,14}",ErrorMessage="{0}输入不正确")]
[RegularExpression(@"\d{11}", ErrorMessage = "手机格式{0}不正确")]
public string stuPhone { get; set}

     上面使用{0}占位符,来显示用户的输入,并且形成友好提示。

10.自定义验证属性

      前面讲的都是预先设定的数据注解,但是系统自由的数据注解肯定不适合所有的场合,所以有时候我们需要自定义数据注解。自定义数据注解有两种,一种是直接写在模型对象中,这样做的好处是验证时只需要关心一种模型对象的验证逻辑,缺点也是显而易见的,那就是不能重用。还有一种是封装在自定义的数据注解中,创建自己的Data Annotations Validation Attribute,优点是可重用,缺点是需要应对不同类型的模型。

(1)创建自定义的属性验证注解属性

      现在我们以封装在自定义数据注解中的方法为例,看下如何在asp.net mvc中自定义数据注解以及使用。首先,所有的数据注解都应继承于System.ComponentModel.DataAnnotations命名空间中的ValidationAttribute类。重写其protected virtual ValidationResult IsValid(object value, ValidationContext validationContext);
using System.ComponentModel.DataAnnotaitons;

namespace ModelValidation.Infrastructure{
public class MustBeTrueAttribute : ValidationAttribute { public override bool IsValid(object value) { return value is bool && (bool)value; } }
}
public class Appointment 
{ 
    [Required(ErrorMessage = "Please enter your name")] 
    [StringLength(50)] 
    public string ClientName { get; set; } 
    [DataType(DataType.Date)]
    [Required(ErrorMessage = "Please choose a date")] 
    public DateTime AppointmentDate { get; set; } 
    [MustBeTrue(ErrorMessage="You must accept the terms")]
   public bool TermsAccepted { get; set; }
}
      
(2)通过内建的验证注解属性进行派生      
       验证规则特性必须集成ValidationAttribute类,当然也可以继承该类的子类。(注意:对于特性约定以Attribute结尾)。我们就自定义一个验证类,实现默认值约束规则DefaultsAttribute,而实现最简单的方式就是自定义正则表达式的规则。定义的验证类的代码如下:
using System.ComponentModel.DataAnnotations;

namespace ModelValidation.Infrastructure{
public class MFAttribute : RegularExpressionAttribute { public MFAttribute(): base("[mf]") { } public override string FormatErrorMessage(string name) { return "性别只能输入m(男)或者f(女)"; } }
}
       [MF]
        public string sex { get; set; }
     在预约这个例子中,Appointment Date应该是个未来的日期,所以可以通过内奸的验证注解属性进行派生。
public class FutureDateAttribute: RequiredAttribute
{
    public override bool IsValid(object value)
    {
        return base.IsValid(value) && ((DateTime)value) >DateTime.Now;
    }
}
[DataType(DataType.Date)]
[FutureDate(ErrorMessage="Please enter a date in the future")]
public DateTime Date { get; set; }
(3)创建模型验证注解属性
      到目前为止,我们所创建的自定义验证注解属性都只能引发属性级验证错误,其实还可以使用注解来验证整个模型,引发模型级错误。
public class NoJoeOnMondaysAttribute:ValidationAttribute
    {
        public NoJoeOnMondaysAttribute()
        {
            ErrorMessage = "Joe cannot book appointments on Mondays";
        }

        public override bool IsValid(object value)
        {
            Appointment app = value as Appointment;
            if(app==null || string.IsNullOrEmpty(app.ClientName) || app.AppointmentDate==null)
            {
                return true;
            }
            else
            {
                return !(app.ClientName == "Joe" && app.AppointmentDate.DayOfWeek == DayOfWeek.Monday); 
            }

        }
    }
[NoJoeOnMondays]
    public class Appointment
    {
        public int AppointmentId { get; set; }
        [Required(ErrorMessage = "Please enter your name")]
        [StringLength(50)]
        public string ClientName { get; set; }
     public string AppointmentDate{get;set;}
    public bool TermsAccepted{get;set;}
  }
      要注意的重要一点是,当检测到属性级问题时,模型级验证属性不会生效。
(4)定义自验证模型
    另一种验证技术是创建自验证模型,即验证逻辑是模型类的一部分,一个自验证模型类实现了IValidatableObject接口。
 public class Appointment:IValidatableObject
    {
        public string ClientName { get; set; }
        
        [DataType(DataType.Date)]
        public DateTime Date { get; set; }

        public bool TermsAccepted { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            List<ValidationResult> errors = new List<ValidationResult>();

            if (string.IsNullOrEmpty(ClientName))
            {
                errors.Add(new ValidationResult("Please enter your name."));
            }

            if(DateTime.Now > Date)
            {
                errors.Add(new ValidationResult("Please enter a date in the future"));
            }

            if (!TermsAccepted)
            {
                errors.Add(new ValidationResult("You must accept the terms"));
            }

            if (errors.Count == 0 && ClientName == "Joe" && Date.DayOfWeek == DayOfWeek.Monday)
            {
                errors.Add(new ValidationResult("Joe cannot book appointments on Mondays"));
            }

            return errors;
        }
    }
11.服务器端方法验证
[Remote(“CheckUserName”, “Account”)]
public string UserName { get; set; }
    然后在AccountController里指定一个CheckUserName办法:
public JsonResult CheckUserName(string username)
{
    if (IsUniqueName(userName) && IsForbiddenName(userName))
    {
        return Json(true, JsonRequestBehavior.AllowGet);
     }
        else if (!IsUniqueName(userName))
     {
        return Json("用户名不唯一!", JsonRequestBehavior.AllowGet);
     }
    else
     {
        return Json("用户名包含违禁词!", JsonRequestBehavior.AllowGet);
     }
}

 三、显示验证消息

 1.显示属性级验证消息

      Html.ValidationMessageFor辅助器为单个模型属性显示验证错误,可以在相应字段旁显示验证错误消息。

<p>留言人邮箱:@Html.TextBoxFor(g=>g.AuthorEmail) @Html.ValidationMessageFor(g=>g.AuthorEmail)</p>
      
      对应css样式,在Style.css中,
.field-validation-error { color: #f00;}   //验证错误是消息文字的颜色
.input-validation-error {                    //输入文本框验证错误时的边框和背景颜色
    border: 1px solid #f00;
    background-color: #fee;
}

     如果验证有效则使用的样式是,

.field-validation-valid {
    display: none;
}
2.显示验证错误摘要
      Html.ValidationSummary辅助器给用户显示了验证错误的摘要。如果没有错误,那么该辅助器不会生成任何HTML。
@using(Html.BeginForm()){

    @Html.ValidationSummary()
<p>留言人邮箱:@Html.TextBoxFor(g=>g.AuthorEmail) </p> <p>标题:@Html.TextBoxFor(g=>g.Title)</p> <p>内容:@Html.TextBoxFor(g=>g.Content,new { style = "width:200px;height:100px;" }) </p> <input type="submit" value="发布" /> }
      对应css样式,在Style.css中,
.validation-summary-errors {
    font-weight: bold;
    color: #f00;
}

.validation-summary-valid {
    display: none;
}

 四、执行客户端验证

      以上的验证技术都是服务器端验证的示例。在Web应用程序中,用户会期望得到即时的验证反馈——对服务器不做任何递交。这成为客户端验证,这通常是用JavaScript实现的。用户输入的数据在被发送给服务器之前就进行验证,给用户提供即时反馈并修正错误的机会。MVC框架支持渐进式客户端验证(Unobrusive Client-Side Validation)。术语“渐进式(Unobtrusive)”意指,在生成的HTML元素上添加验证标签属性来表示验证规则。这些标签属性由包含在MVC框架中的JavaScript库进行解释,框架又转而对jQuery验证库进行配置,由验证库完成实际的验证工作。在实际工作中,可以选择对属性级问题使用客户端验证,而对整个模型则使用服务器端验证。

1.启用客户端验证

(1)Web.config文件中的两个设置

...
<appSettings>
    ...
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  ...
</appSettings>

    为了使客户端验证生效,这两个设置都必须为true。也可以配置基于个别视图的客户端验证,只需在视图的一个razor代码块中设置HtmlHelper.ClientValidationEnabled和HtmlHelper.UnobtrusiveJavaScriptEnabled即可。

(2) 添加NuGet包

      添加处理那些注解属性的JavaScript包,并检查用户在表单中输入的数据。在Visual StudioTools->Library Package Manager菜单中选择Package Manager Console,并输入如下命令:

Install-Package jQuery -version 1.10.2
Install-Package jQuery.Validation -version 1.11.1
Install-Package Microsoft.jQuery.Unobtrusive.Validation -version 3.0.0

     这些包将文件添加到Scripts文件夹,可以使用script元素将它们添加到布局中。

      添加到布局中的script元素的次序是很重要的,必须先添加jQuery库,后面跟随jQuery验证库,最后添加微软渐进式验证库。

(3)使用客户端验证

      一旦已经启用了客户端验证,并确保在布局中引用了JavaScript库,就可以执行客户端验证了。最简单的方式是运用前面服务器端验证所使用的元数据注解属性,如Required、Range以及StringLength等。

    public class Appointment
    {
        [Required]
        [StringLength(10,MinimumLength =3)]
        public string ClientName { get; set; }

        [DataType(DataType.Date)]
        public DateTime Date { get; set; }

        public bool TermsAccepted { get; set; }
    }

 

      此时在浏览器中呈现的反馈是即时的,不需要形成对服务器的请求。执行验证的JavaScript代码会组织递交表单,直到不再出现验证错误为止。当用户改正错误时,反馈是即时的。这种动态反馈帮助用户提供应用程序所需要的数据,而不必将表单提交到服务并等待响应。

      MVC客户端验证的一个好处是,运用于客户端和服务器端的指定验证规则的属性是相同的。这意味着,从不支持JavaScript的浏览器而来的数据会得到同样的验证,而不需要任何额外的努力

 

posted @ 2015-09-28 14:30  RunningYY  阅读(1430)  评论(2编辑  收藏  举报