数据注解和验证

来源:互联网 发布:污软件有哪些 编辑:程序博客网 时间:2024/06/06 11:40

本章主要内容 

●  利用数据注解进行验证 

●  如何创建自定义的验证逻辑 

●  模型元数据注解的用法 

 

     对于Web开发人员来说,用户输入验证一直是一个挑战。不仅在客户端浏览器中需要执行验证逻辑,在服务器端也需要执行。客户端验证逻辑会对用户向表单中输入的数据给出一个即时的反馈,这也是时下Web应用程序所期望的特性。之所以需要服务器端验证逻辑,主要是因为来自网络的信息都是不能信任的。 

      然而,一旦从全局来看,就会发现逻辑仅仅是整个验证的很小一部分。验证首先需要管理用户友好(通常是本地化)的并与验证逻辑相关的错误提示消息;当验证失败时,再把这些错误提示消息呈现在用户界面上,当然还要向用户提供从验证失败中恢复的机制。 

      如果觉得验证是令人望而生畏的繁杂琐事,那么值得高兴的是ASP.NET MVC框架可以帮助处理这些琐事。本章将专注于讲解ASP.NET MVC框架验证组件的相关知识。 

    当在ASP.NET MVC设计模式上下文中谈论验证时,主要关注的是验证模型的值。用户输入了需要的值吗?是要求范围内的值吗?当发现ASP.NET MVC框架的验证特性也关注于验证模型时,这也就不奇怪了。因为框架是可扩展的,所以可以采用任意想要的方式构建验证模式,但是默认的方法是一种声明式验证,它采用了本章讲到的数据注解特性。 

      本章首先讲解数据注解如何与ASP.NET MVC框架配合工作,然后介绍注解的用途,不单单局限于验证这一方面。注解是一种通用机制,可以用来向框架注入元数据,同时,框架不只驱动元数据的验证,还可以在生成显示和编辑模型的HTML标记时使用元数据。

下面首先介绍一下验证的应用场合。

 

6.1    为验证注解订单 

 

     在MVC Music Store购买音乐的顾客会有一个典型的购物车结算环节。这个环节需要付款和收货信息。Order(由下面的代码实现)中包含了应用程序完成结算环节所需要的所有信息: 

public class Order 

   public int OrderId { get; set; } 

   public System.DateTime OrderDate { get; set; } 

   public string Username { get; set; } 

   public string FirstName { get; set; } 

   public string LastName { get; set; } 

   public string Address { get; set; } 

   public string City { get; set; } 

   public string State { get; set; } 

   public string PostalCode { get; set; } 

   public string Country { get; set; } 

   public string Phone { get; set; } 

   public string Email { get; set; } 

   public decimal Total { get; set; } 

   public List<OrderDetail> OrderDetails { get; set; } 

     Order类的一些属性需要由顾客直接输入(FirstNameLastName属性),但其他属性的值,应用程序可以通过其他方式获得,例如从运行环境中获得或从数据库中查找(Username属性,由于顾客在结算之前必定已经登录系统,因此运行环境中已经有这个

值了)。 

     应用程序使用HTML辅助方法EditorForModel来构建结算页面。下面是Views/Checkout文件夹中视图文件AddressandPayment.cshtml里的部分代码: 

<fieldset> 

   <legend>Shipping Information</legend> 

   @Html.EditorForModel() 

</fieldset> 

     EditorForModel辅助方法为模型对象的每一个属性构建一个编辑器,生成的表单如图6-1所示。 

 

图  6-1 

 

     这个表单存在一些明显的问题。比如图6-1中显示出的OrderIdOrderDate编辑器,这些属性值并不需要顾客填写,应用程序会在服务器端设置。同样,输入框上面的标签名对程序员来说有一定的意义(FirstName 明显是个属性名),但顾客面对这个标签时,就会

摸不着头绪(难道某个开发人员的空格键坏了吗),本章在后面部分会讲解这些问题的解决方法。 

     在图6-1中还有个更严但重看不出来的问题:顾客可以在完全没有填写表单的情况下单击表单底部的“Submit Order”按钮,应用程序也不会提醒他们必须提供像姓名和地址这样非常重要的信息。下面介绍的数据注解功能将会很好地解决这些问题。 

     6.1.1   验证注解的使用 

     数据注解特性定义在名称空间System.ComponentModel.DataAnnotations(但接下来将看到,有些特性不是定义在这个名称空间中)。它们提供了服务器端验证的功能,当在模型的属性上使用这些特性之一时,框架也支持客户端验证。在名称空间DataAnnotations中,4个特性可以用来应对一般的验证场合。下面从Required特性开始对它们逐一介绍。 

     1.  Required 

     因为顾客的姓氏和名字都是必需的,所以需要在模型类OrderFirstNameLastName属性上面添加Required特性: 

[Required] 

public string FirstName { get; set; } 

 

[Required] 

public string LastName { get; set; } 

 

     当这两个属性中的一个是null或空时,Required特性将会引发一个验证错误(稍后将会讲解如何处理验证错误)

   像所有内置的验证特性一样,Required特性既传递服务器端验证逻辑也传递客户端的验证逻辑(尽管在MVC框架内部是另一个组件通过一个验证适配器设计来传递的该特性的客户端验证逻辑)。 

     添加该特性后,如果顾客在没有填写姓氏的情况下提交表单,就会出现图6-2所示的默认错误提示消息。 

 

图  6-2 

 

     然而,即使顾客在客户端的浏览器中没有设置允许JavaScript 执行的权限,验证逻辑也会在服务器端捕获到一个空名字的属性。即便正确地实现了控制器操作(稍后就会讲解)顾客也还是会看到图6-2所示截图中显示的错误提示消息。 

 

     2.   StringLength 

     现在已经要求顾客必须输入名字,但如果他输入了一个非常长的名字,该怎么处理呢?Wikipedia中讲到,名字最长的是费城的一个德裔排字工人,他的全名超过了500个字符。虽然.NET中的String字符串理论上可以存储上GBUnicode字符,但MVC Music Store

的数据库模式设置了名字的最大长度是160个字符。如果试图向数据库中插入一个超过最大长度的名字,就会出现异常。这就是StringLength特性的用武之地,它可以确保顾客提供的字符串长度符合数据库模式的要求: 

[Required] 

[StringLength(160)] 

public string FirstName { get; set; } 

 

[Required] 

[StringLength(160)] 

public string LastName { get; set; } 

     这里要注意一下对同一个属性设置多个验证特性的方式。设置了StringLength特性后,顾客如果输入了过多的字符,就会看到LastName输入框下方的默认错误提示消息,如图6-3所示。 

 

图  6-3 

 

     名为MinimumLength的参数是一个可选项,它可以用来设定字符串的最小长度。下面的代码设置了FirstName属性,要求顾客至少要包含3(小于等于160)字符的属性值才能通过验证: 

[Required] 

[StringLength(160, MinimumLength=3)] 

public string FirstName {get; set;} 

 

     3.  RegularExpression 

     模型类Order的一些属性要求的不只是简单的非空或长度验证。例如,某些订单的Email属性需要的是一个有效可用的e-mail地址。然而事实上,在不向该地址发送一封邮件等待响应的情况下,确保一个e-mail地址的可用性是不切实际的。我们所能做的就是使

用正则表达式来使输入的字符串看起来像可用的e-mail地址: 

[RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}")] 

public string Email { get; set; } 

 

     正则表达式是一种检查字符串格式和内容的简洁有效的方式。如果顾客输入的e-mail地址不能和正则表达式匹配,那么将会看到如图6-4所示的错误提示消息。 

 

图  6-4 

 

     对于非专业开发人员来说(甚至对一些专业开发人员来说也是如此),这一错误提示消息看起来就像是胡乱敲击键盘产生的乱码,没有任何实际意义。鉴于此,接下来将会介绍如何设置人性化的错误提示消息。 

 

     4.  Range 

     Range特性用来指定数值类型值的最小值和最大值。如果MVC  Music  Store仅面向中年顾客提供服务的话,就可以在Order类中添加Age属性并按照下面的代码在其上添加Range特性:

[Range(35,44)] 

public int Age {get; set;} 

 

     该特性的第一个参数设置的是最小值,第二个参数设置的是最大值,这两个值也包含在范围之内。Range特性既可用于int 类型,也可用于double类型。它的构造函数的另外一个重载版本中有一个Type类型的参数和两个字符串(这样就可以给date属性和decimal

属性添加范围限制了)。 

[Range(typeof(decimal), "0.00", "49.99")] 

public decimal Price {get; set;} 

 

     5.  System.Web.Mvc下的验证特性 

     ASP.NET MVC框架还为应用程序在名称空间System.Web.Mvc中额外添加了两个验证特性。其中一个是Remote特性。Remote特性允许利用服务器端的回调函数执行客户端的验证逻辑。以MVC Music StoreRegisterModel类的UserName属性为例,系统中不允许

两个用户具有相同的UserName值,但是在客户端很难验证以确保UserName属性值的唯一性(除非把所有的用户名都从数据库传送到客户端)。使用Remote特性可以把UserName的值传到服务器,然后在服务器端的数据库中与相应的表字段值进行比较: 

[Remote("CheckUserName", "Account")] 

public string UserName {get; set;} 

 

     在特性中可以设置客户端代码要调用的控制器名称和操作名称。客户端代码将自动把用户输入的UserName属性值发送到服务器,该特性的一个重载构造方法还允许指定要发送给服务器的其他字段: 

public JsonResult CheckUserName(string username) 

   var result = Membership.FindUsersByName(username).Count == 0; 

   return Json(result, JsonRequestBehavior.AllowGet); 

 

     上面的控制器操作将利用与UserName属性同名的参数进行验证,并返回一个封装在JavaScript Object Notation(JSON)对象中的布尔类型值(truefalse)

     第二个是Compare特性,它用于确保模型对象的两个属性拥有相同的值。例如,为了避免顾客输入错误,往往要求输入两次e-mail地址: 

[RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}")] 

public string Email { get; set; } 

 

[Compare("Email")] 

public string EmailConfirm { get; set; } 

 

     如果顾客两次输入的e-mail地址不一致,将会看到如图6-5所示的错误提示消息。 

 

图  6-5 

 

     正是由于数据注解的可扩展性,才导致了RemoteCompare特性的存在。本章后面部分将会讲解如何创建自定义注解。下面介绍如何在验证失败时创建自定义的错误提示消息。 

    

     6.1.2  自定义错误提示消息及其本地化 

     每个验证特性都允许传递一个带有自定义错误提示消息的参数。例如,如果不喜欢与RegularExpression特性关联的默认错误提示消息(因为它显示的是正则表达式),可以使用下面的代码自定义错误提示消息: 

[RegularExpression(@" [A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}", 

ErrorMessage="Email doesnt look like a valid email  

address.")] 

public string Email {get; set;} 

     ErrorMessage是每个验证特性中用来设置错误提示消息的参数的名称: 

[Required(ErrorMessage="Your last name is required")] 

[StringLength(160, ErrorMessage="Your last name is too long")] 

public string LastName {get; set;} 

     自定义的错误提示消息在字符串中也有一个格式项。内置特性使用友好的属性显示名称格式化错误提示消息字符串(本章后面将会详述如何在显示注解中设置显示名称)。作为一个例子,请看下面代码中的Required特性: 

[Required(ErrorMessage="Your {0} is required")] 

[StringLength(160, ErrorMessage="{0} is too long")] 

public string LastName {get; set;} 

     该特性使用了带有格式项(0)的错误提示消息。如果客户不填写LastName,就会看到如图6-6所示的错误提示消息。 

 

图  6-6 

 

     如果应用程序是面向国际市场开发的,那么这种硬编码错误提示消息的技术就不大实用了。届时就不简单是像上面这样显示的固定文本,而是为不同的地区显示不同的文本内容。幸好,所有的验证特性都允许为本地化的错误提示消息指定资源类型名称和资源名称。 

[Required(ErrorMessageResourceType=typeof(ErrorMessages), 

        ErrorMessageResourceName="LastNameRequired")] 

[StringLength(160, ErrorMessageResourceType = typeof(ErrorMessages), 

           ErrorMessageResourceName = "LastNameTooLong")] 

public string LastName { get; set; } 

     上面的代码假设在项目中有一个名为ErrorMessages.resx的资源文件,并且其中包含所需要的条目(LastNameRequiredLastNameTooLong)。在ASP.NET中,要使用本地化的资源文件,需要将当前线程的UICulture属性设置为相应的地域语言。想要了解更多的信息,请参阅“How  to  :  Set  the  Culture  and  UI  Culture  for  ASP.NET  Page  Globalization”,网址为

http://msdn.microsoft.com/en-us/library/bz9tc508.aspx。 

    

     6.1.3  注解的后台原理 

     在介绍控制器和视图中的验证错误如何协调工作以及如何创建自定义的验证特性之前,理解验证特性的内部机制是很有必要的。ASP.NET MVC的验证特性是由模型绑定器、模型元数据、模型验证器和模型状态组成的协调系统的一部分。 

     1.   验证和模型绑定 

     在阅读验证注解部分时,可能会有几个疑问:验证是什么时候发生的?如何才能知道验证失败? 

     默认情况下,ASP.NET MVC框架在模型绑定时执行验证逻辑。在操作方法带有参数时,模型绑定将隐式地执行: 

[HttpPost] 

public ActionResult Create(Album album) 

   // the album parameter was created via model binding 

   // .. 

     当然,也可以利用控制器的UpdateModelTryUpdateModel方法显式地执行模型绑定: 

[HttpPost] 

public ActionResult Edit(int id, FormCollection collection) 

   var album = storeDB.Albums.Find(id); 

 

   if(TryUpdateModel(album)) 

   { 

// ... 

   } 

 

     模型绑定器一旦使用新值完成对模型属性的更新,就会利用当前的模型元数据获得模型的所有验证器。ASP.NET MVC运行时提供了一个验证器(DataAnnotationsModelValidator)来与数据注解一同工作。这个模型验证器会找到所有的验证特性并执行它们包含的验证逻辑。模型绑定器捕获所有失败的验证规则并把它们放入模型状态中。 

 

     2.  验证和模型状态 

     模型绑定主要的副产品就是模型状态(利用Controller派生类对象的ModelState属性可以访问到)。模型状态不仅包含了用户所有想放入模型属性里的值,也包括与每一个属性相关联的所有错误(还有所有与模型对象本身有关的错误)。如果在模型状态中存在错误,那

ModelState.IsValid就返回false。 

     例如,假设顾客在没有填写LastName值的情况下,提交了结算表单。由于设置了Required验证注解特性,因此在模型绑定之后,下面的所有表达式将会返回true: 

ModelState.IsValid = = false 

ModelState.IsValidField("LastName") = = false 

ModelState("LastName").Errors.Count > 0 

     也可以在模型状态中查看与失败验证相关的错误提示消息: 

var lastNameErrorMessage = ModelState("LastName").Errors[0].ErrorMessage; 

     当然,通常很少编写代码来查看特定的错误提示消息。如同运行时自动地向模型状态注入验证错误信息一样,它也能够自动地从模型状态中提取错误信息。内置的HTML辅助方法可以利用模型状态(和模型状态中出现的错误)来改变模型在视图中的显示。例如,ValiadationMessage辅助方法可以通过查看模型状态来显示与特定部分的视图数据相关的错误提示消息: 

@Html.ValiadationMessageFor(m => m.LastaName) 

     控制器操作通常需要关心的问题是:模型状态是否有效? 

 

     6.1.4  控制器操作和验证错误 

     控制器操作决定了在模型验证失败和验证成功时的执行流程。在验证成功时,操作通常会执行必要的步骤来保存或更新客户的信息。当验证失败时,操作一般会重新渲染提交模型值的视图。这可以让用户看到所有的验证错误提示消息,并按照提示改正输入错误或补填遗漏的字段信息。下面代码中的AddressAndPayment操作就展示了这一典型的操作行为: 

[HttpPost] 

public ActionResult AddressAndPayment(Order newOrder) 

   if (ModelState.IsValid) 

   {              

      newOrder.Username = User.Identity.Name; 

      newOrder.OrderDate = DateTime.Now; 

      storeDB.Orders.Add(newOrder); 

      storeDB.SaveChanges(); 

 

        // Process the order 

      var cart = ShoppingCart.GetCart(this); 

      cart.CreateOrder(newOrder);                

      return RedirectToAction("Complete", new { id = newOrder.OrderId }); 

   } 

   // Invalid -- redisplay with errors 

   return View(newOrder);         

     上面的这段代码将立即检查ModelStateIsValid 属性。模型绑定器已经构建好一个Order类对象,并用请求中(提交的表单)的值填充这个Order类对象。当模型绑定器完成订单的更新之后,它将会执行所有与这个对象关联的验证规则。因此,可以知道这个对象是

否处于正确的状态。也可以通过显式地调用UpdateModelTryUpdateModel来实现这个操作,如下面的代码所示: 

[HttpPost] 

public ActionResult AddressAndPayment(FormCollection collection) 

 var newOrder = new Order(); 

 TryUpdateModel(newOrder); 

   if (ModelState.IsValid) 

   {                

      newOrder.Username = User.Identity.Name; 

      newOrder.OrderDate = DateTime.Now; 

      storeDB.Orders.Add(newOrder); 

      storeDB.SaveChanges(); 

 

        // Process the order 

      var cart = ShoppingCart.GetCart(this); 

      cart.CreateOrder(newOrder);                

      return RedirectToAction("Complete", new  

{ id = newOrder.OrderId }); 

   } 

   // Invalid -- redisplay with errors 

   return View(newOrder);         

     针对这个问题有很多种不同的处理方法,但是注意上面实现的两段代码都检查了模型状态的有效性。如果模型状态无效,操作就会重新渲染AddressAndPayment视图,给用户一个修正验证错误并重新提交表单的机会。 

     通过上面的讲解,可以看到数据注解特性给验证带来的简易性和透明性。当然,这些内置的特性不可能满足应用程序中可能遇到的所有验证场合。框架提供了简易的方法来创建自定义的验证方法,以满足特殊场合。 

 

     6.2   自定义验证逻辑 

     ASP.NET MVC框架的扩展性意味着实现自定义验证逻辑有着很大的可行性。本节重点介绍两个核心应用方法: 

          ●  将验证逻辑封装在自定义的数据注解中。 

          ●  将验证逻辑封装在模型对象中。 

     把验证逻辑封装在自定义数据注解中可以轻松地实现在多个模型中重用逻辑。当然,这样需要在特性内部编写代码以应对不同类型的模型,但是一旦实现了,新的注解就可以在多处重用。 

     另一方面,如果将验证逻辑直接放入模型对象中,就意味着验证逻辑可以很容易地编码实现,因为这样只需要关心一种模型对象的验证逻辑,但是这种方式不利于实现逻辑的重用。 

     下面几节将详细介绍这两种方式,首先介绍自定义数据注解方式。 

 

     6.2.1   自定义注解 

     假设要限制顾客输入姓氏中单词的数量,例如姓氏中单词的数量不得超过10 个,并且还要让这种验证(限定一个string类型的最大单词数)Music  Store应用程序的其他模型中重用。如果是这样,可以考虑把验证逻辑封装在一个可重用的特性中。所有的验证注解(RequiredRange)特性最终都派生自基类ValidationAttribute,它是个抽象类,定义在名称空间System.ComponentModel.DataAnnotations中。同样,程序员的验证逻辑也必须派生自ValidationAttribute的类: 

using System.ComponentModel.DataAnnotations; 

 

namespace MvcMusicStore.Infrastructure 

   public class MaxWordsAttribute : ValidationAttribute 

   { 

 

   } 

     为了实现这个验证逻辑,至少需要重写基类中提供的IsValid 方法的其中一个版本。重IsValid方法时利用的ValidationContext参数,提供了很多可以在IsValid方法内部使用的信息,如模型类型、模型对象实例、用来验证属性的友好的显示名称以及其他有用的信息。 

public class MaxWordsAttribute : ValidationAttribute 

   protected override ValidationResult IsValid( 

          object value, ValidationContext validationContext) 

     { 

 

          return ValidationResult.Success; 

     } 

     IsValid方法中的第一个参数是要验证的对象的值。如果这个对象的值是有效的,就可以返回一个成功的验证结果,但是在判断它是否有效之前,需要知道单词数的上限。要获得这一上限,可以通过向这个特性添加一个构造函数来要求顾客将最大单词数作为一个参数传递给它: 

public class MaxWordsAttribute : ValidationAttribute 

{        

     public MaxWordsAttribute(int maxWords) 

     { 

    _maxWords = maxWords; 

     } 

 

  protected override ValidationResult IsValid( 

       object value, ValidationContext validationContext) 

  { 

 

       return ValidationResult.Success; 

  } 

 

    private readonly int _maxWords; 

     既然已经参数化了最大的单词数,下面就可以实现验证逻辑来捕获错误了: 

public class MaxWordsAttribute : ValidationAttribute 

public MaxWordsAttribute(int maxWords) 

    _maxWords = maxWords; 

 

protected override ValidationResult IsValid( 

   object value, ValidationContext validationContext) 

   if (value != null) 

    { 

        var valueAsString = value.ToString(); 

             if (valueAsString.Split(' ').Length > _maxWords) 

           { 

                 return new ValidationResult("Too many words!"); 

            } 

      } 

      return ValidationResult.Success; 

  } 

 

  private readonly int _maxWords; 

     上面的代码通过使用Split方法以空格作为分隔符来分隔输入值并统计生成的字符串数量,对输入字符串的单词数目进行简单的验证。如果单词数目超过了上限,系统就会返回一个带有硬编码错误提示消息的ValidationResult对象,以告知验证失败。 

     上面代码中的问题在于硬编码的错误提示消息那句代码。使用数据注解的开发人员希望可以使用ValidationAttributeErrorMessage属性来自定义错误提示消息。同时还要跟其他验证特性一样,提供一个默认的错误提示消息(在开发人员没有提供自定义的错误提示消息时使用)并且还要利用验证的属性名称生成错误提示消息: 

public class MaxWordsAttribute : ValidationAttribute 

   public MaxWordsAttribute(int maxWords) 

      :base("{0} has too many words.") 

   { 

      _maxWords = maxWords; 

   } 

 

   protected override ValidationResult IsValid( 

          object value, ValidationContext validationContext) 

   { 

      if (value != null) 

      { 

           var valueAsString = value.ToString(); 

           if (valueAsString.Split(' ').Length > _maxWords) 

           { 

              var errorMessage = FormatErrorMessage( 

                              validationContext.DisplayName); 

              return new ValidationResult(errorMessage); 

          } 

       } 

           return ValidationResult.Success; 

  } 

 

  private readonly int _maxWords; 

     前面的代码做了两处改动: 

         ●  首先,向基类的构造函数传递了一个默认的错误提示消息。如果正在面向国际开发应用程序的话,就应该从一个资源文件中提取这个默认的错误提示消息。 

         ●  注意,默认的错误提示消息中包含了一个参数占位符({0})。这个占位符之所以存在,是因为第二处改动,即调用继承的FormatErrorMessage方法将会自动地使用显示的属性名称来格式化这个字符串。FormatErrorMessage可以确保我们使用正确的错误提示消息字符串(即使这个字符串是存储在一个本地资源文件中)。这条代码语句需要传递name属性的值,这个值可以通过validationContext参数的DisplayName性获得。构造完成验证逻辑之后,就可以将其应用到任何模型属性上: 

[Required] 

[StringLength(160)] 

[MaxWords(10)] 

public string LastName{get; set;} 

     甚至可以赋予特性自定义的错误提示消息: 

[Required] 

[StringLength(160)] 

[MaxWords(10, ErrorMessage="There are too many words in {0}")] 

public string LastName{get; set;} 

现在,如果顾客输入了过多的单词,他将会在视图中看到如图6-7所示的提示消息。 

 

图  6-7 

 

     自定义特性只是向模型提供逻辑验证的一种方式。正如刚才看到的,特性是很容易在很多不同模型类中实现复用的。第8章将讲解如何为MaxWordsAttribute特性添加客户端验证能力。 

 

     6.2.2  IValidatableObject 

     自验证(self-validating)模型是指一个知道如何验证自身的模型对象。一个模型对象可以通过实现IValidatableObject 接口来实现对自身的验证。为了演示这个方法,下面在Order模型中直接实现对LastName字段中单词个数的检查: 

public class Order : IValidatableObject 

   public IEnumerable<ValidationResult> Validate( 

                                 ValidationContext validationContext) 

   { 

      if (LastName != null && 

         LastName.Split(' ').Length > 10) 

      { 

         yield return new ValidationResult("The last name has too many  

     MaxWordsAttribute可以以NuGet包的形式获得。搜索Wrox.ProMvc3. Validation.MaxWordsAttribute并将相应代码添加到项目中。 

words!", 

                                                      new []{"LastName"}); 

      } 

  } 

 

  // rest of Order implementation and properties 

  // ... 

     这种方式与特性版本有几个明显的不同点: 

          ●  MVC运行时为执行验证而调用的方法的名称是Validate而不是IsValid,但更重要的是,它们的返回类型和参数也不相同。 

          ●  Validate的返回类型是IEnumerable<ValidationResult>,而不是单独的ValidationResult对象。因为从表面上看,内部的验证逻辑验证的是整个模型,因此可能返回不止一个验证错误。 

          ●  这里没有value参数传递给Validate方法,因为在此Validate是一个模型实例方法,在其内部可以直接访问当前模型对象的属性值。 

     注意上面的代码使用了C#yield  return语法来构建枚举返回值,同时代码还需要显式地告知ValidationResult与之关联的字段的名称(在这个例子中字段的名称是LastName但是ValidationResult的构造函数的最后一个参数是String类型的数组,因为这样可以使结

果与多个属性关联)。 

     许多验证场合通过IValidatableObject 方式都可以更容易地实现,尤其是在需要比较模型的多个属性的应用场合中。 

     到目前为止,我们已经对所有需要知道的验证注解做了介绍,但是ASP.NET MVC架中还有其他一些注解,它们能够影响运行时显示和编辑模型的方式。在前面介绍“友好的显示名称”时,提到了这些注解,现在是深入了解这些内容的时候了。 

 

     6.3   显示和编辑注解 

     在本章的开始部分,我们为顾客创建一个表单来提交订单处理所需要的信息。当时是使用HTML辅助方法EditorForModel实现的,但生成的表单与期望不符,图6-8会帮助您唤醒记忆。 

 

图  6-8 

     在这个截图中,可以明显地看出两个问题: 

          ●  不应该显示Username字段(它是由控制器操作中的代码来填充和管理的)。 

          ●  FirstName字段的FirstName两个单词中间应该有一个空格。 

     解决这些问题的方法也在名称空间DataAnnotations中。 

     和前面看到的验证特性一样,模型元数据提供器会收集下面的显示(和编辑)注解信息,以供HTML辅助方法和ASP.NET MVC运行时的其他组件使用。HTML辅助方法可以使用任何可用的元数据来改变模型的显示和编辑UI。 

 

     6.3.1    Display 

     Display特性可以为模型属性设置友好的“显示名称”。这里就可以使用Display特性修FirstName字段的标签显示名称: 

[Required]        

[StringLength(160, MinimumLength=3)] 

[Display(Name="First Name")] 

public string FirstName { get; set; } 

     加上这个特性后,视图将会渲染得如图6-9所示。 

 

图  6-9 

     这样看起来更好看了。 

     除了名字之外,Display特性还可以控制UI上属性的显示顺序。例如,要实现对LastNameFirstName编辑框显示次序的控制,可以使用下面的代码: 

[Required] 

[StringLength(160)] 

[Display(Name = "Last Name", Order = 15001)] 

[MaxWords(10, ErrorMessage = "There are too many words in {0}")] 

public string LastName { get; set; } 

 

[Required]        

[StringLength(160, MinimumLength=3)]       

[Display(Name="First Name", Order=15000)] 

public string FirstName { get; set; }        

     假设在Order模型中没有其他的属性有Display特性,那么表单中最后两个字段的顺序应该先是FirstName,然后才是LastNameOrder参数的默认值是10 000,各个字段将按照这个值升序排列。 

 

     6.3.2    ScaffoldColumn 

     ScaffoldColumn特性可以隐藏HTML辅助方法(EditorForModelDisplayForModel)渲染的一些属性: 

[ScaffoldColumu(false)] 

public string Username {get; set;} 

     添加上这个特性之后,EditorForModel辅助方法将不再为Username字段显示输入元素label 标签。然而这里需要注意的是,如果模型绑定器在请求中看到匹配的值,它仍然试图为Username属性赋值。在第7章将深入介绍这个应用(称为重复提交)。 

     尽管上面介绍的这两个特性足以应对订单表单的所有显示场合,但下面我们仍然继续讲解和ASP.NET MVC 3结合使用的其他注解。 

 

     6.3.3    DisplayFormat 

     通过命名参数,DisplayFormat特性可以用来处理属性的各种格式化选项。当属性包含空值时,可以提供可选的显示文本,也可以为包含标记的属性关闭HTML编码,还可以为运行时指定一个应用于属性值的格式化字符串。下面的代码可以将模型的Total属性值格式化为货币值形式: 

[DisplayFormat(ApplyFormatInEditMode=true, DataFormatString="{0:c}")] 

public decimal Total {get; set;} 

     ApplyFormatInEditMode参数的值默认是false,所以如果想把Total属性格式化为表单输入元素,需要将属性ApplyFormatInEditMode的值设置为true。例如,当把模型中decimal类型的Total属性值设置为12.1时,将会在视图中看到如图6-10所示的输出。 

 

图  6-10 

     之所以将ApplyFormatInEditMode参数的默认值设为false,其中一个主要原因是ASP.NET MVC模型绑定器不能解析格式化的值用于显示。在这个例子中,由于字段中包含有货币符号,模型绑定器将不能解析提交回的价格值。因此,应该将属性ApplyFormatInEditModel的值设为false。 

 

     6.3.4    ReadOnly 

      如果想确保默认的模型绑定器不使用请求中的新值更新属性,可以在属性上添加ReadOnly特性: 

[ReadOnly(true)] 

public decimal Total {get; set;} 

     注意这里的EditorForModel辅助方法仍会为Total属性显示一个可用的输入元素,因此只有模型绑定器考虑ReadOnly特性。  

 

     6.3.5    DataType 

     DataType特性可以为运行时提供关于属性的特定用途信息。例如,String类型的属性可以应用于很多场合——可以保存e-mail地址、URL或是密码。DataType可以满足所有这些需求。如果看过MVC Music Store的账户登录模型,就会发现下面的代码: 

[Required] 

[DataType(DataType.Password)] 

[Display(Name ="Password")] 

public string Password {get; set;} 

     对于一个Name参数为PasswordDataTypeASP.NET MVC中的HTML编辑器辅助方法将会渲染一个type 特性值为“password”的输入元素。这就意味着当在浏览器中输入密码时,就看不到输入的字符了(如图6-11所示)。 

 

图  6-11 

     其他的数据类型还有CurrencyDateTimeMultilineText。 

 

     6.3.6    UIHint 

     UIHint特性给ASP.NET MVC运行时提供了一个模板名字,以备调用模板辅助方法(DisplayForEditorFor)渲染输出时使用。也可以定义自己的模板辅助方法来重写ASP.NET MVC的默认行为,第14章将会介绍如何自定义模板。 

 

     6.3.7    HiddenInput 

     HiddenInput 在名称空间System.Web.Mvc中,它可以告知运行时渲染一个type 特性值为“hidden”的输入元素。隐藏输入是一种保存表单中信息的很好方式,但是用户在浏览器中不能看到和编辑这些数据(因为恶意用户可以通过改变提交的表单值来改变输入的值,所以不要想当然地认为这个特性是万无一失的),以便浏览器将原有数据返回给服务器。 

 

     6.4   小结 

     本章首先介绍了应用于验证的数据注解,而后介绍了ASP.NET MVC运行时如何在Web应用程序中使用模型元数据、模型绑定器以及HTML辅助方法来构建良好的验证逻辑。这些验证不需要重复的代码就可以在服务器端和客户端都提供验证特性。还介绍了如何为自定义的验证逻辑构建自定义的注解,并与自验证模型进行了比较。最后阐述了如何使用数据注解来影响视图中HTML辅助方法的HTML输出。 

 

 

    

原创粉丝点击