Asp.Net MVC4 系列--进阶篇之Model(2)

在上一章介绍了MVC中model binding,本章将介绍一下MVCmodel validation

和前几章一样,从一个例子开始

准备工作

 

准备model

   public classAppointment
    {
        public string ClientName { get; set; }
       [DataType(DataType.Date)]
        public DateTime Date { get; set; }
        public bool TermsAccepted { get; set; }
}


代码说明:一个预约model,包含了客户姓名,预约日期,以及是否accept合约

 

准备controller

    

public class AppintmentController : Controller
    {
 
        public ViewResult MakeBooking()
        {
            return View("NewAppintment",new  Appointment { Date = DateTime.Now });
        }
 
        [HttpPost]
        public ViewResult MakeBooking(Appointment appt)
        {
            //statements to store new Appointment in a
            //repository would go here in a real project
            return View("Completed", appt);
        }
 
}

代码说明:很经典的”Get-Post-action” 结构,同名方法两个,Get版本用于服务第一次来到页面的请求,post版本用于处理提交页面的请求。

Get版本:render预约页面,获取预约信息

Post版本:获得预约的信息,透传(为了演示)给CompletedView表示预约成功

 

 

准备View

a.      (NewAppintment.cshtml)

@model MVCModelValidation.Models.Appointment
@{
ViewBag.Title = "Make A Booking";
}
<h4>Book an Appointment</h4>
@using (Html.BeginForm()) {
<p>Your name: @Html.EditorFor(m =>m.ClientName)</p>
<p>Appointment Date:@Html.EditorFor(m => m.Date)</p>
<p>@Html.EditorFor(m =>m.TermsAccepted) I accept the terms & conditions</p>
<input  type="submit" value="Make Booking" />
}


代码说明:预约View

 

b.     Completed

@model MVCModelValidation.Models.Appointment
@{
ViewBag.Title = "Completed";
}
<h4>Your appointment is confirmed</h4>
<p>Your name is:<b>@Html.DisplayFor(m => m.ClientName)</b></p>
<p>The date of your appointment is:<b>@Html.DisplayFor(m => m.Date)</b></p>


 

代码说明:预约成功 View

 

 

运行


Make booking


 

可以看到,代码有几个需要验证的地方:

1.      客户姓名必填

2.      预约日期必填

3.      必须accept 合约

 

 

显式验证

由于我们的验证是针对用户提交表单的scenario,因此修改post版本的action就好了:

带validation的action:


[HttpPost]
        public ViewResult MakeBooking(Appointment appt)
        {
            if(string.IsNullOrEmpty(appt.ClientName))
            {
               ModelState.AddModelError("ClientName", "Please enter yourname");
            }
            if(ModelState.IsValidField("Date") && DateTime.Now >appt.Date)
            {
               ModelState.AddModelError("Date", "Please enter a date inthe future");
            }
            if (!appt.TermsAccepted)
            {
               ModelState.AddModelError("TermsAccepted", "You mustaccept the terms");
            }
           if (ModelState.IsValid)
            {
                // statements to store newAppointment in a
                // repository would go here ina real project
                return View("Completed", appt);
            }
 
            // statements to store newAppointment in a
            // repository would go here in areal project
            return View("NewAppintment", appt);
        }


 

代码说明:由于到达这一步我们model已经被完成binding了(具体参见上一章asp.netMVC4系列—进阶篇之Model(1)),因此我们拿出值添加我们的验证逻辑,把相应错误信息放在ModelError里面让MVCFramework拿走,最后判断Model.IsValid,如果没有任何错误,这个属性为True。那么这些信息如何在View中显示出来呢?

 

修改NewAppointment.cshtml


@model MVCModelValidation.Models.Appointment
@{
ViewBag.Title = "Make ABooking";
}
<h4>Book an Appointment</h4>
@using (Html.BeginForm()) {
     @Html.ValidationSummary() 
<p>Your name:@Html.EditorFor(m => m.ClientName)</p>
<p>Appointment Date:@Html.EditorFor(m => m.Date)</p>
<p>@Html.EditorFor(m =>m.TermsAccepted) I accept the terms & conditions</p>
<input type="submit" value="Make Booking" />
}


我们只添加了一行:

    @Html.ValidationSummary()

这样,就可以display我们action中添加的错误信息了。

运行

 


不填名字也不勾选terms& conditions,make booking


可以看到,我们手动的验证逻辑工作了,并且错误信息也显示了。

如果需要改显示出来的错误信息的css,可以修改:

~/Content/Site.css中的.validation-summary-errors

Html.ValidationSummary的其他重载

Html.ValidationSummary(bool)

是否只显示model级别的error(后面会演示)

Html.ValidationSummary(string)

在所有错误信息显示前,显示一条总的错误信息

Html.ValidationSummary(bool,string)

前两种情况的组合

 

Model 级别的错误

依旧修改刚才post版本的MakeBookingAction

添加一个验证逻辑:


 if (ModelState.IsValidField("ClientName") &&ModelState.IsValidField("Date") && appt.Date.DayOfWeek ==DayOfWeek.Monday)
            {
               ModelState.AddModelError("", "Sorry , Can notappointments on Mondays");
            }


 

在View中使用另一个Html.ValidationSummary的重载(只显示model error):

    @Html.ValidationSummary(true)


 

填所有信息(日期选一个monday),尝试makebooking


 

 


可以看到,modellevel的验证生效,并且只显示了modellevel的错误信息。

 

显示Property级别的信息

修改NewAppointment.cshtml(Form部分):

 

@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<p>@Html.ValidationMessageFor(m=> m.ClientName)</p>
<p>Your name:@Html.EditorFor(m => m.ClientName)</p>
<p>@Html.ValidationMessageFor(m=> m.Date)</p>
<p>Appointment Date:@Html.EditorFor(m => m.Date)</p>
<p>@Html.ValidationMessageFor(m=> m.TermsAccepted)</p>
<p>@Html.EditorFor(m =>m.TermsAccepted) I accept the terms & conditions</p>
<input  type="submit" value="MakeBooking" />
}


我们对每个需要显示错误信息的字段添加了Html.ValidationMessageFor,这样就可以了。

 

运行

 

为了验证View的逻辑,把所有信息清空


Make booking


可以看到我们为每个字段添加的ValidationForMessage方法生效了,并显示了property级别的错误信息

 

把验证放在metadata完成

MVC Framework把验证作为了一个concern,也就是AOP的思想。MVCFramework 给我们提供了足够的Validation的Attribute让我们使用,我们可以在model里面根据需要添加不同的Attribute就可以完成我们的验证,避免在Controller中出现类似的重复的验证逻辑,而controller只需要专注自己的职责(操作domainmodel,选择View)

 

重构代码

 

1.      Model

public class Appointment
    {
       [Required(ErrorMessage = "Please provide a name")]
       public string ClientName { get; set; }
 
       [Required(ErrorMessage = "Please enter a date")]
       [DataType(DataType.Date)]
       public DateTime Date { get; set; }
 
       [Range(typeof(bool), "true", "true", ErrorMessage ="You must accept the terms")]
       public bool TermsAccepted { get; set; }
}


 

2.      把Controller的验证逻辑拿掉

 

 [HttpPost]
       public ViewResult MakeBooking(Appointment appt)
       {
       
 
           if (ModelState.IsValid)
           {
                // statements to store newAppointment in a
                // repository would go here ina real project
               return View("Completed", appt);
           }
 
           // statements to store new Appointment in a
           // repository would go here in a real project
           return View("NewAppintment", appt);
       }


 

3.      View不用改

 

4.      运行

 



可以看到,重构后(meta data版本)的代码工作了。

 

Validation Attribute

Compare

传入类型,指定一个值和要对比的值

Range

传入一个范围

RegularExpression

指定正则表达式

Required

非空

StringLength

限制字符串长度

 

Customize Attribute

 

1.      继承Validation Attribute

 

public class MustBeTrueAttribute : ValidationAttribute {
public override bool IsValid(object value) {
return value is bool && (bool)value;
}
}


 

2.      使用

 

public class Appointment
    {
        [Required(ErrorMessage ="Please provide a name")]
        public string ClientName {get; set; }
 
        [Required(ErrorMessage ="Please enter a date")]
        [DataType(DataType.Date)]
        public DateTime Date { get;set; }
 
        [MustBeTrue(ErrorMessage ="You must accept the terms")]
        public bool TermsAccepted {get; set; }
       
}


 

3.      运行


Make booking


 

继承built-in的attribute

1.      实现

public class FutureDateAttribute : RequiredAttribute
    {
        public override bool IsValid(object value)
        {
            return base.IsValid(value) && ((DateTime)value) > DateTime.Now;
        }
}


 

2.      使用

  public class Appointment
    {
       [Required(ErrorMessage = "Please provide a name")]
        public string ClientName { get; set; }
 
       [FutureDate(ErrorMessage = "Please Enter a Future Date")]
       [DataType(DataType.Date)]
        public DateTime Date { get; set; }
 
       [MustBeTrue(ErrorMessage = "You must accept the terms")]
        public bool TermsAccepted { get; set; }
       
    }


 

3.      运行


Make booking

 


 

可以看到futuredate 验证生效,更新一下日期提交

可以看到预约成功。


 

Model level 验证

1.      实现

public class NoMondayBookingAttribute : ValidationAttribute
    {
        public NoMondayBookingAttribute()
        {
            ErrorMessage ="Can not book appointments on Mondays temporary";
        }
        public override bool IsValid(object value)
        {
            var app = value asAppointment;
            if (app == null )
            {
                // we don‘t have amodel of the right type to validate, or we don‘t have
                // the values forthe ClientName and Date properties we require
                return true;
            }
            return app.Date.DayOfWeek != DayOfWeek.Monday;
        }
    }


 

代码说明:验证是否为appointment类型,如果是,取出日期,如果为周一返回false,其他情况返回true

 

2.      使用

[NoMondayBooking]
    public class Appointment
    {
        [Required(ErrorMessage ="Please provide a name")]
        public string ClientName {get; set; }
 
        [FutureDate(ErrorMessage ="Please Enter a Future Date")]
        [DataType(DataType.Date)]
        public DateTime Date { get;set; }
 
        [MustBeTrue(ErrorMessage ="You must accept the terms")]
        public bool TermsAccepted {get; set; }
       
}


3.      运行

输入所有信息,日期选择下周一,符合所有property验证的要求


Make booking

 


可以看到,由于预约为下周一,因此modellevel的验证没过,而我们View的实现没改,我们现在是ValidateSummary(true),只显示model level的错误信息。

我们改到下周二重新提交

 


 

可以看到预约成功,通过了model level验证。

 

IValidatableObject

 

如果希望把validation的职责加在model上,可以实现IValidatableObject接口,下一版model代码:

 

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)
        {
            var 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 (errors.Count == 0&& Date.DayOfWeek == DayOfWeek.Monday)
            {
                errors.Add(
                    new ValidationResult("cannot book appointments on Mondays"));
            }
            if (!TermsAccepted)
            {
                errors.Add(new ValidationResult("You must accept the terms"));
            }
            return errors;
        }
}


 

这样就实现了和前面示例验证逻辑一模一样的验证,可以仍推荐使用AOP的思路(Attribute)进行validate。

 

客户端验证

MVC framework提供unobstrusive javascript使得我们可以方便的进行客户端验证

1.      Appsetting添加

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


 

2.      需要的js:

/Scripts/ jquery-1.7.1.min.js
/Scripts/ jquery.validate.min.js
/Scripts/ jquery.validate.unobtrusive.min.js


通常,创建basic的mvc sln默认的jsbundle已添加(BundleConfig.cs):

  bundles.Add(newScriptBundle("~/bundles/jqueryval").Include(
                       "~/Scripts/jquery.unobtrusive*",
                       "~/Scripts/jquery.validate*"));


在View中Render:

@Scripts.Render("~/bundles/jqueryval")


 

 

分析开启客户端验证后生成了一个htmlinput

<input class="text-box single-line" data-val="true"
data-val-length="The field ClientName must be a string with aminimum length of 3 and
a maximum length of 10." data-val-length-max="10" data-val-length-min="3" 
data-val-required="The ClientName field is required."id="ClientName" 
name="ClientName" type="text" value=""/>

MVC Framework会找到data-val属性,如果为true说明需要客户端验证,然后解析data-val开头的验证类型,例如data-val-length,data-val-length-min,

data-val-length-max . data-val-required 等等。

MVC framework的客户端验证部分,是基于jquery的。

 

Asp.Net MVC4 系列--进阶篇之Model(2),古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。