使用Visual Studio 2010和MVC 2.0增强验证功能

开发 后端
本篇文章主要关注使用DataAnnotations来对model进行验证,同时会对最新版本特性进行简单的接触,以及如何使用Visual Studio 2010和MVC 2.0增强验证功能。

在开始之前,我不得不说明我已经安装了Visual Studio 2010 RC1,并使用它将老版本转换为ASP.Net 4.0,大多数情况下,当你接收到来自用户从form表单post来的信息后,你的验证代码往往会检查相应的值是否存在,数据类型是否正确以及数据的范围是否正确。

如果将验证代码放到一个集中的地方时,那类似上面所说的改变会不会变得更简单些?Model中的DataAnnotations正是为此而来,在MVC 2.0中,这一特性被包含在内。

DataAnnotations作为.net Framework的一部分已经有一段时间了,但是MVC 2.0中增加了ModelMetaData类,这是储存MetaData的容器,默认会使用同样也是新增类的DataAnnotationsMetaDataProvider类。因为传入的值会由Action方法接受model binding作为匹配传入参数和action的参数而介入。

在MVC 2.0中,默认的model binder使用DataAnnotationsMetaDataProvider来获取metadata中model binder尝试匹配的对象,如果验证用的metadata存在,则其会通过对对象的属性和传入的值比较来进验证,这类meta由你通过使用标签(Attribute)修饰属性来实现。

下面例子中我通过原程序中添加联系人这一过程来描述使用DataAnnotatioins的方法,这里我们使用自定义ViewModel,名为:ContactPersonViewModel。通过Contact.Add()这个action方法来添加联系人,代码如下:

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Web.Mvc;  
  4. using System.ComponentModel;  
  5.  
  6. namespace ContactManagerMVC.Views.ViewModels  
  7. {  
  8.   public class ContactPersonViewModel  
  9.   {  
  10.     public int Id { get; set; }  
  11.     public string FirstName { get; set; }  
  12.     public string MiddleName { get; set; }  
  13.     public string LastName { get; set; }  
  14.     public DateTime DateOfBirth { get; set; }  
  15.     public IEnumerable<SelectListItem> Type { get; set; }  
  16.   }  
  17. }  

下面,我在为属性添加一些标签(Attribute):

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Web.Mvc;  
  4. using System.ComponentModel.DataAnnotations;  
  5. using ContactManagerMVC.Attributes;  
  6. using System.ComponentModel;  
  7.  
  8. namespace ContactManagerMVC.Views.ViewModels  
  9. {  
  10.   public class ContactPersonViewModel  
  11.   {  
  12.     public int Id { get; set; }  
  13.     [Required(ErrorMessage = "Please provide a First Name!")]  
  14.     [StringLength(25, ErrorMessage = "First name must be less than 25 characters!")]  
  15.     [DisplayName("First Name")]  
  16.     public string FirstName { get; set; }  
  17.  
  18.     [DisplayName("Middle Name")]  
  19.     public string MiddleName { get; set; }  
  20.  
  21.     [Required(ErrorMessage = "Please provide a Last Name!")]  
  22.     [StringLength(25, ErrorMessage = "Last name must be less than 25 characters!")]  
  23.     [DisplayName("Last Name")]  
  24.     public string LastName { get; set; }  
  25.  
  26.     [Required(ErrorMessage = "You must provide a Date Of Birth!")]  
  27.     [BeforeTodaysDate(ErrorMessage = "You can't add someone who hasn't been born yet!")]  
  28.     [DisplayName("Date Of Birth")]  
  29.     public DateTime? DateOfBirth { get; set; }  
  30.  
  31.     public IEnumerable<SelectListItem> Type { get; set; }  
  32.   }  
  33. }  

上面标签的绝大多数标签都是在System.ComponentModel.Annotations命名空间内,只有RequiredAttribute标签不在此命名空间内,这个标签声明此值必须是一个有效值,并且包含ErrorMessage属性。这个属性可以让你传入自定义错误信息。StringLengthAttribute标签指定了属性可以接受的最小值和***值范围。当和RequiredAttribute标签结合使用时,只需要设置可以接受的***值。DisplayNameAttribute用于设置属性如何显示。#p#

上面标签中BeforeTodaysDateAttribute标签并不是.net Framework所提供,这是一个自定义标签,用于检测日期是否比当前的日期要早,你可以看到ErrorMessage值被设置。这个标签用于防止任何被添加到联系人列表的联系人还未出生,下面是这个标签的代码:

  1. using System.ComponentModel.DataAnnotations;  
  2. using System;  
  3.  
  4. namespace ContactManagerMVC.Attributes  
  5. {  
  6.   public class BeforeTodaysDateAttribute : ValidationAttribute  
  7.   {  
  8.     public override bool IsValid(object value)  
  9.     {  
  10. if (value == null)  
  11. {  
  12.   return true;  
  13. }  
  14. DateTime result;  
  15. if (DateTime.TryParse(value.ToString(), out result))  
  16. {  
  17.   if (result < DateTime.Now)  
  18.   {  
  19.     return true;  
  20.   }  
  21. }  
  22. return false;  
  23.     }  
  24.   }  
  25. }  

很简单是吧,这个类继承了ValidationAttribute并重写了IsValid()虚方法,如果未提供值,或是值小于当前日期(DateTime.Now),则返回True.利用标签(Attribute)的方式让在一个集中的地方应用验证规则变得简单,现在,只要ContactPersonViewModel在程序中被用到了,则验证规则同时也会被应用到。但现在DefaultModelBinder内的DataAnnotations被支持,下面来看新版本的Add Partial View:

  1. <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ContactPersonViewModel>" %> 
  2.  
  3. <script type="text/javascript"> 
  4.   $(function() {  
  5.   $('#DateOfBirth').datepicker({ dateFormat: 'yy/mm/dd' });  
  6.   });  
  7.   $('#save').click(function () {  
  8. $.ajax({  
  9.     type: "POST",  
  10.     url: $("#AddContact").attr('action'),  
  11.     data: $("#AddContact").serialize(),  
  12.     dataType: "text/plain",  
  13.     success: function (response) {  
  14.   if (response == "Saved") {  
  15. window.location = "/";   
  16.   }else {  
  17. $("#details").html(response);  
  18.   }  
  19.     }  
  20. });  
  21.   });  
  22. </script> 
  23.  
  24. <% using (Html.BeginForm("Add", "Contact", FormMethod.Post, new { id = "AddContact" }))  
  25.    {%> 
  26. <table> 
  27.   <tr> 
  28. <td class="LabelCell"><%= Html.LabelFor(m => m.FirstName)%> </td> 
  29. <td><%= Html.TextBox(m => m.FirstName)%>   
  30. <%= Html.ValidationMessageFor(m => m.FirstName)%></td>    
  31.   </tr> 
  32.   <tr> 
  33. <td class="LabelCell"><%= Html.LabelFor(m => m.MiddleName)%> </td> 
  34. <td><%= Html.TextBox(m => m.MiddleName)%></td>    
  35.   </tr> 
  36.   <tr> 
  37. <td class="LabelCell"><%= Html.LabelFor(m => m.LastName)%> </td> 
  38. <td><%= Html.TextBox(m => m.LastName)%>   
  39. <%= Html.ValidationMessageFor(m => m.LastName)%></td>    
  40.   </tr> 
  41.   <tr> 
  42. <td class="LabelCell"><%= Html.LabelFor(m => m.DateOfBirth)%> </td> 
  43. <td><%= Html.TextBox(m => m.DateOfBirth)%>   
  44. <%= Html.ValidationMessageFor(m => m.DateOfBirth)%></td>    
  45.   </tr> 
  46.   <tr> 
  47.     <td class="LabelCell"><%= Html.LabelFor(m => m.Type)%></td> 
  48.     <td><%= Html.DropDownList("Type")%> 
  49.     </td> 
  50.   </tr> 
  51.   <tr> 
  52.     <td class="LabelCell"></td> 
  53.     <td><input type="button" name="save" id="save" value="Save" /></td> 
  54.   </tr> 
  55. </table> 
  56. <% } %>  

可以看出,这里使用新的强类型Html Helper.对前面项目修改的两处是利用了jQuery代码。***处是添加联系人的Partial View是通过AJax提交,如果验证失败,则添加的form会再次被显示,如果验证通过,新的联系人被添加到列表中,页面会刷新继而显示更新后包含新联系人的列表。

由于下面几种原因,原来的Action方法需要被修正。首先修改action方法使其接受ContactPersonViewModel而不是ContactPerson作为参数,这是因为相关的验证规则应用于ContactPersonViewModel,如果不将参数类型改变,那model binder依然能将传入的值和ContactPerson的属性相匹配,但所有的验证规则就不复存在了。第二个改变是检查ModelState的IsValid属性是否有效,否则整个验证就变得毫无意义.

  1. [AcceptVerbs(HttpVerbs.Post)]  
  2. public ActionResult Add([Bind(Exclude = "Id, Type")]ContactPersonViewModel person)  
  3. {  
  4.  
  5.     if (ModelState.IsValid)  
  6.     {  
  7.   var p = new ContactPerson  
  8.   {  
  9. FirstName = person.FirstName,  
  10. MiddleName = person.MiddleName,  
  11. LastName = person.LastName,  
  12. Type = Request.Form["Type"].ParseEnum<PersonType>()  
  13.   };  
  14.   if (person.DateOfBirth != null)  
  15. p.DateOfBirth = (DateTime)person.DateOfBirth;  
  16.   ContactPersonManager.Save(p);  
  17.   return Content("Saved");  
  18.     }  
  19.     var personTypes = Enum.GetValues(typeof(PersonType))  
  20.     .Cast<PersonType>()  
  21.     .Select(p => new  
  22.     {  
  23.   ID = p,  
  24.   Name = p.ToString()  
  25.     });  
  26.     person.Type = new SelectList(personTypes, "ID", "Name");  
  27.     return PartialView(person);  
  28. }   
  29.  

在model绑定过程中,我去掉了id和Type属性,因为在把联系人添加到数据库以前并不会存在id属性,而去掉Type属性是因为在ViewModel中它的类型是SelectList,但在BLL层中ContactPerson对象中却是枚举类型,如果ModelState的IsValid属性为True(注:既验证通过),则ViewModel的属性会和ContactPerson对象的属性进行匹配,如果IsValid不为True,数据会回传到View中显示验证失败的相关信息。

上面代码中我们注意到了Request.Form[“Type”]这个string类型的ParseEnum<T>扩展方法,这也是为什么我去掉Type属性,只有这样它才会被转换为适当的类型。扩展方法的原型(在我的Google Analytics 文中)如下:

  1. public static T ParseEnum<T>(this string token)  
  2. {  
  3.     return (T)Enum.Parse(typeof(T), token);  
  4. }  
  5.  
  6. edit  

这个action方法也是如此,除了对DateOfBirth进行编辑那部分:

  1. <tr> 
  2.   <td class="LabelCell"><%= Html.LabelFor(m => m.DateOfBirth)%> </td> 
  3.   <td><%= Html.EditorFor(m => m.DateOfBirth)%>   
  4. <%= Html.ValidationMessageFor(m => m.DateOfBirth)%></td>    
  5. </tr>  

这里我并没有使用TextBoxFor<T>扩展方法,而是使用了EditorFor<T>方法,默认情况下,DateTime类型都以hh:mm:ss这样的方式显示,但我并不喜欢这种格式,所以我创建了一个格式化显示时间日期的模板,在View/Shared目录下,我添加了一个名为EditorTemplates(这也是MVC中应有命名方式,因为MVC会自动搜索这个位置)并在此目录下添加一个名为DateTime的Partial View,这也是MVC的惯例,而DateTime.ascx的代码如下:

  1. <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<System.DateTime?>" %> 
  2. <%= Html.TextBox("", Model.HasValue ? Model.Value.ToShortDateString() : string.Empty) %> 

虽然只有短短两行代码,但是可以让时间日期如果为空时,什么都不显示,而如果时间存在,则以ShortDate的格式显示。

总结

本篇文章研究了ASP.Net MVC 2.0中利用DataAnnotations来进行验证,现在这已经是.net framework的一部分。文中还简单的接触了新版本中的一些特性,包括强类型的HTML Helper以及模板。本篇文章的代码使用Visual Studio 2010 RC1创建的,所以代码不能在VWD和Visual Studio的环境中调试。

Visual Studio 2010 全球发布会

【编辑推荐】

  1. 升级Visual Studio 2010和.Net 4注意要点
  2. 专家揭秘Visual Basic 2010的十大新特性
  3. Visual Studio 2010 Ultimate中MSF过程模型设计
  4. Visual Studio 2010代码编译器特性分析
  5. 详解Visual Studio 2010五大新特性
责任编辑:王晓东 来源: 博客园
相关推荐

2010-02-26 09:18:24

Visual Stud

2010-04-12 08:43:45

Visual Stud

2023-09-26 00:24:44

VisualStudio视图

2009-04-23 14:05:28

Visual Stud历史调试功能

2009-10-22 09:47:33

Visual Stud

2010-09-25 08:50:00

Visual Stud

2009-03-17 08:56:57

Visual StudVS2010C++

2009-12-02 09:43:38

Visual Stud

2009-08-26 09:26:04

Visual Stud

2009-08-21 13:29:20

Visual Stud

2009-11-19 10:55:33

Visual Stud

2010-03-16 14:32:16

Visual Stud

2009-11-10 13:43:37

Visual Stud

2009-12-01 19:12:41

Visual Stud

2010-04-15 08:40:00

UML建模Visual Stud

2009-11-19 09:59:47

Visual Stud

2009-12-02 10:44:30

Visual Stud

2009-03-10 10:21:05

灾难恢复Restart Manvs

2010-02-23 10:39:43

Visual Stud

2010-02-04 09:17:26

Visual Stud
点赞
收藏

51CTO技术栈公众号