本篇文章跟大家分享关于ASP.NET MVC的数据验证,主要内容如下:
1. 了解数据验证
2. 显式地添加验证
3. 显示验证结果
4. 使用元数据进行验证
5. 执行客户端验证
6. 避免对ORM对象的标注在重新编译时被覆盖掉
在使用ASP.NET MVC框架进行开发时,什么时候会进行数据验证呢?首先在用户提交表单钱,需要对用户的输入进行验证,这个过程一般由程序员自己实现。然后,如果这一步验证成功了,用户提交表单,路由系统处理URL请求,然后找到特定的Controller的Action来响应该请求,在调用该Action前,会先将用户提交的所有参数提取出来,作为参数传递给Action,提取并传递的过程称为模型绑定(Model Binding),而MVC框架在执行模型绑定的过程中,难免会进行一些强制类型转换,这也会对输入参数进行验证,当然,这个验证过程是由MVC框架自动完成的。
验证包含两个部分,第一部分是校验用户的输入是否合法有效,第二部分是,如果输入不合法则提示用户进行修改。ASP.NET MVC框架对验证有着非常好的支持。
在使用MVC框架以前,我们最常用的验证方式是JQuery来进行简单的验证,如某个文本框不能为空、必须是数字等,也可以通过Ajax技术来访问数据库以验证某个用户名是否存在。
MVC框架提供的对一种方式是通过使用ModelState类来手工地进行验证。在用户将数据POST到后台以后,程序员需要对用户的输入进行判断,如果输入无效,则调用ModelState.AddModelError(name,errorMessage)方法将错误信息记录下来,并返回到页面上,代码如下:
View部分:
@model MvcApp.Models.Appointment
@{
ViewBag.Title = "Make A Booking";
}
Book an Appointment
@using (Html.BeginForm()) {
Your name: @Html.EditorFor(m => m.ClientName)
Appointment Date: @Html.EditorFor(m => m.Date)
@Html.EditorFor(m => m.TermsAccepted) I accept the terms & conditions
"submit" value="Make Booking" />
}
Controller部分:
[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 (!appt.TermsAccepted) {
ModelState.AddModelError("TermsAccepted",
"You must accept the terms");
}
if (ModelState.IsValid) {
repository.SaveAppointment(appt);
return View("Completed", appt);
} else {
return View();
}
}
当用户的输入不合法时,程序会直接return View(),由于View中的文本框是通过@Html.EditorFor方法生成的,所以,该文本框在出错的情况下会显示红色的边框和粉色的背景色,但没有错误消息。
如果想要显示错误消息,需要在页面上调用@Html.ValidationMessageFor(m => m.ClientName),这个方法是显示某个字段的错误信息的。如果希望显示整个对象的错误信息汇总,则调用@Html.ValidationSummary(),它会把该对象的所有属性的错误消息汇总到一个列表中进行显示。
@Html.ValidationSummary()方法有几个重载的版本,没有参数的版本就默认显示出所有的错误信息,Html.ValidationSummary(bool)这个版本,如果这个参数为True的话,就只显示对象级别的错误信息(下面会说明什么是对象级别的错误信息)。Html.ValidationSummary(string)这个版本,会把这个字符串参数显示在所有错误信息的前面。Html.ValidationSummary(bool,string)这个版本,就不做介绍了,是上面所有版本合体。
所谓对象级别的错误信息,是相对于属性级别的错误信息而言的。ModelState.AddModelError(name,errorMessage)这个方法,如果name参数不为空,则这个错误信息就是属于name所代表的属性的,属于属性级别的错误信息,而如果name属性为空,则这个错误信息是属于当前对象的,就是对象级别的错误信息。
通过ModelState类来手工地进行验证,验证的逻辑是写在特定的控制器的方法中,没有有效的进行关注点的分离,并且不能很好的进行代码重用,所以,MVC框架还提供了更加整洁的用法,就是DataAnnotation的方式。如:
public class Appointment {
[Required]
public string ClientName { get; set; }
[DataType(DataType.Date)]
[Required(ErrorMessage="Please enter a date")]
public DateTime Date { get; set; }
[Range(typeof(bool), "true", "true", ErrorMessage="You must accept the terms")]
public bool TermsAccepted { get; set; }
}
这个数据标注的功能是相当强大,它不仅可以自动完成验证的过程,并且还同时实现了客户端和服务器端的验证。当用户提交数据时,首先会在客户端进行校验,有错误信息时会直接显示出来,并且不会把无效数据提交给服务器。如果客户端验证通过了,(或者用户禁用了JS,)则会提交数据到服务器。这个请求首先由路由系统处理。当这个请求被某个路由匹配以后,ModelBinder会对参数进行处理,这个过程中会调用所有的验证类,如果有错误就会在ModelState中记录下来。如果ModelState.IsValid为true的话,参数才会被传递到控制器的方法中,否则会把错误信息返回到页面上进行显示,显示的方法上文已经介绍过了。也就是说,标注一个属性,这个验证规则会在浏览器段和服务器端都进行校验,并且这个过程是自动完成的。
ASP.NET MVC框架自动开启了客户端的验证,在配置文件中
<configuration>
<appSettings>
<add key="ClientValidationEnabled" value="true"/>
<add key="UnobtrusiveJavaScriptEnabled" value="true"/>
appSettings>
......
那么,客户端的验证是怎样自动实现的呢?
如果开启了客户端,调用Html.EditorFor时,会生成类似下面的HTML代码
<input class="text-box single-line" data-val="true"
data-val-length="The field ClientName must be a string with a minimum length of 3 and a maximum length of 10."
data-val-lengthmax="10" data-val-length-min="3"
data-val-required="The ClientName field is required."
id="ClientName" name="ClientName" type="text" value=""
/>
这些特殊的属性是约定好的用来验证的属性,MVC项目中的JQuery代码已经对这些特定的属性进行了处理,甚至,我们可以手动的给HTML元素添加这些约定好的属性,有兴趣的可以自行研究。
但是遗憾的是,客户端自动生成的HTML属性只支持系统定义好的这些验证类,下面是几个常用的系统定义的验证类的介绍:
Required用来指明某个属性是必填的,StringLength规定了某个字段的最大长度,Range来指明某个属性的值的范围,RegularExpression规定了某个属性的值必须符合特定的正则表达式。
这些个验证类能够完成一些常见的验证,但是并不能满足所有要求,幸运的是,MVC框架可以很轻松的自定义一个验证规则,只需要实现ValidationAttribute接口,例如:
public class MustBeTrueAttribute : ValidationAttribute {
public override bool IsValid(object value) {
return value is bool && (bool)value;
}
}
这样使用
[MustBeTrue(ErrorMessage="You must accept the terms")]
public bool TermsAccepted { get; set; }
MustBeTrueAttribute是一个验证属性的类,当然也可以自定义一个用来验证对象的类,验证对象的类可以对这个对象的多个属性进行综合验证,实现的方法与验证属性的类非常类似,区别在于,IsValid方法的参数是一个对象,而不是一个属性的值,并且,这个验证类需要标注在一个实体类的上方,而不是属性的上方。如:
[XXXValidation]
public class TestClass
{
}
自定义的验证类也能够实现在客户端的验证,但需要自己来编写一些JS代码,过程有些许的曲折,这里就不做介绍了。
还有一种情况需要介绍。如果实体类是通过类似EntityFramework等ORM框架自动生成的,那么,直接在实体类上进行标注的话,重新编译后,标注的代码就会消失。解决方法如下:
这个是自动生成的类
//------------------------------------------------------------------------------
//
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
//
//------------------------------------------------------------------------------
namespace ValidationDemo
{
using System;
using System.Collections.Generic;
public partial class Employee
{
public Employee()
{
this.Employees1 = new HashSet();
this.Orders = new HashSet();
this.Territories = new HashSet();
}
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Title { get; set; }
}
}
我们需要编写一个元数据类,然后再将这个元数据类标记为Employee部分类的元数据,代码如下:
namespace ValidationDemo
{
public partial class EmployeeMetaData
{
[Required]
public string LastName { get; set; }
[Required]
[StringLength(20)]
public string FirstName { get; set; }
}
[MetadataType(typeof(EmployeeMetaData))]
public partial class Employee
{
}
}
这样就解决了元数据被覆盖的问题。