使用Visual Studio 2010和MVC 2.0增强验证功能
创始人
2024-06-27 15:01:43
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 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 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" %> 
  2.  
  3.  
  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.  
  23.  
  24. <% using (Html.BeginForm("Add", "Contact", FormMethod.Post, new { id = "AddContact" }))  
  25.    {%> 
  26.  
  27.   
  28.  
  29. <%= Html.LabelFor(m => m.FirstName)%>  
  30.     
  31.   
  32.  
  33.   
  34.  
  35. <%= Html.LabelFor(m => m.MiddleName)%>  
  36.     
  37.   
  38.  
  39.   
  40.  
  41. <%= Html.LabelFor(m => m.LastName)%>  
  42.     
  43.   
  44.  
  45.   
  46.  
  47. <%= Html.LabelFor(m => m.DateOfBirth)%>  
  48.     
  49.   
  50.  
  51.   
  52.  
  53.     <%= Html.LabelFor(m => m.Type)%> 
  54.     
  55.  
  56.   
  57.  
  58.   
  59.  
  60.      
  61.     
  62.  
  63.   
  64.  
  65. <%= Html.TextBox(m => m.FirstName)%>   
  66. <%= Html.ValidationMessageFor(m => m.FirstName)%>
  67. <%= Html.TextBox(m => m.MiddleName)%>
    <%= Html.TextBox(m => m.LastName)%>   
  68. <%= Html.ValidationMessageFor(m => m.LastName)%>
  69. <%= Html.TextBox(m => m.DateOfBirth)%>   
  70. <%= Html.ValidationMessageFor(m => m.DateOfBirth)%>
  71. <%= Html.DropDownList("Type")%> 
  72.     
  73.  
  74. <% } %>  

可以看出,这里使用新的强类型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()  
  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()  
  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扩展方法,这也是为什么我去掉Type属性,只有这样它才会被转换为适当的类型。扩展方法的原型(在我的Google Analytics 文中)如下:

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

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

  1.  
  2.   <%= Html.LabelFor(m => m.DateOfBirth)%>  
  3.   <%= Html.EditorFor(m => m.DateOfBirth)%>   
  4. <%= Html.ValidationMessageFor(m => m.DateOfBirth)%>    
  5.   

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

  1. <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> 
  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五大新特性

相关内容

热门资讯

PHP新手之PHP入门 PHP是一种易于学习和使用的服务器端脚本语言。只需要很少的编程知识你就能使用PHP建立一个真正交互的...
网络中立的未来 网络中立性是什... 《牛津词典》中对“网络中立”的解释是“电信运营商应秉持的一种原则,即不考虑来源地提供所有内容和应用的...
各种千兆交换机的数据接口类型详... 千兆交换机有很多值得学习的地方,这里我们主要介绍各种千兆交换机的数据接口类型,作为局域网的主要连接设...
粉嫩如何诠释霸道 东芝M805... “霸道粉”是个什么玩意东芝M805拿过来的时候,笔者扑哧笑了,不是笑这款笔记本,而是笑这款产品的颜色...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
什么是大数据安全 什么是大数据... 在《为什么需要大数据安全分析》一文中,我们已经阐述了一个重要观点,即:安全要素信息呈现出大数据的特征...
只需三步,让你掌握阿尔法路由器... 请参照以下阿尔法路由器设置方法,通过浏览器进入及设置宽带路由器。经过这三个步骤的设置,基本上能够熟悉...
如何更省钱? 近期特价热门硒鼓... 如今激光打印设备已经成为商务或企业的绝对主力办公设备,但采购者往往忽视了激光打印设备所用的耗材,相对...
全面诠释网络负载均衡 负载均衡的出现大大缓解了服务器的压力,更是有效的利用了资源,提高了效率。那么我们现在来说一下网络负载...
如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...