【译】使用VS2010和MVC2.0增强验证功能-阿里云开发者社区

开发者社区> 范大脚脚> 正文

【译】使用VS2010和MVC2.0增强验证功能

简介:
+关注继续查看
     MVC2.0的RTM版本终于在2010年3月11日推出,借此,我也为我的Contact Manager application增加一些新的特性。尽管本篇文章主要关注使用DataAnnotations来对model进行验证,但我也会对最新版本特性进行简单的接触。

 

      在开始之前,我不得不说明我已经安装了VS 2010 RC1,并使用它将老版本转换为ASP.Net 4.0(老版本可以点击这里下载)

 

       大多数情况下,当你接收到来自用户从form表单post来的信息后,你的验证代码往往会检查相应的值是否存在,数据类型是否正确以及数据的范围是否正确。至少,你应该保证每当一个函数接受用户输入时,就应该执行相应的验证逻辑。这意味着有些时候在整个程序中的不同位置,你会对同一个或者相似的值进行多次验证。比如说你想限制用户的姓氏不大于20个字符,那这样的验证需要在程序的好几个部分实现。直到Jan Vennegoor of Hesselink的出现(荷兰球队的一名球员,目前为doomed Hull City FC效力),在他的薪水还没有随着他服役的球队被降级以前,他来到你的网站想购买一个价值50,000英镑的耳环,他以他22个字符长度的姓氏注册,但他发现他的姓氏不在允许的范围之内时,他很气愤的离开你的网站并且给web master留下不满的评论。很明显,你可不想让类似的情况出现,所以你不得不在你的程序中找到所有验证姓氏长度的相关代码,并一一修正以便能接受更长的姓氏……

 

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

  

       DataAnnotations作为.net Framework的一部分已经有一段时间了,但是MVC2.0中增加了ModelMetaData类,这是储存MetaData的容器,默认会使用同样也是新增类的DataAnnotationsMetaDataProvider类。因为传入的值会由Action方法接受model binding作为匹配传入参数和action的参数而介入,在MVC2.0中,默认的model binder使用DataAnnotationsMetaDataProvider来获取metadata中model binder尝试匹配的对象,如果验证用的metadata存在,则其会通过对对象的属性和传入的值比较来进验证,这类meta由你通过使用标签(Attribute)修饰属性来实现。

 

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

using System;
using System.Collections.Generic;
using System.Web.Mvc;
using System.ComponentModel;

namespace ContactManagerMVC.Views.ViewModels
{
  public class ContactPersonViewModel
  {
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public IEnumerable<SelectListItem> Type { get; set; }
  }
}
 

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

using System;
using System.Collections.Generic;
using System.Web.Mvc;
using System.ComponentModel.DataAnnotations;
using ContactManagerMVC.Attributes;
using System.ComponentModel;

namespace ContactManagerMVC.Views.ViewModels
{
  public class ContactPersonViewModel
  {
    public int Id { get; set; }
    [Required(ErrorMessage = "Please provide a First Name!")]
    [StringLength(25, ErrorMessage = "First name must be less than 25 characters!")]
    [DisplayName("First Name")]
    public string FirstName { get; set; }

    [DisplayName("Middle Name")]
    public string MiddleName { get; set; }

    [Required(ErrorMessage = "Please provide a Last Name!")]
    [StringLength(25, ErrorMessage = "Last name must be less than 25 characters!")]
    [DisplayName("Last Name")]      
    public string LastName { get; set; }

    [Required(ErrorMessage = "You must provide a Date Of Birth!")]
    [BeforeTodaysDate(ErrorMessage = "You can't add someone who hasn't been born yet!")]
    [DisplayName("Date Of Birth")]
    public DateTime? DateOfBirth { get; set; }

    public IEnumerable<SelectListItem> Type { get; set; }
  }
}
 

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

 

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

using System.ComponentModel.DataAnnotations;
using System;

namespace ContactManagerMVC.Attributes
{
  public class BeforeTodaysDateAttribute : ValidationAttribute
  {
    public override bool IsValid(object value)
    {
      if (value == null)
      {
        return true;
      }
      DateTime result;
      if (DateTime.TryParse(value.ToString(), out result))
      {
        if (result < DateTime.Now)
        {
          return true;
        }
      }
      return false;
    }
  }
}
 

     很简单是吧,这个类继承了ValidationAttribute并重写了IsValid()虚方法,如果未提供值,或是值小于当前日期(DateTime.Now),则返回True.

      利用标签(Attribute)的方式让在一个集中的地方应用验证规则变得简单,现在,只要ContactPersonViewModel在程序中被用到了,则验证规则同时也会被应用到。这常常如此(http://www.asp.net/learn/mvc/tutorial-39-cs.aspx),但现在DefaultModelBinder内的DataAnnotations被支持,下面来看新版本的Add Partial View:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ContactPersonViewModel>" %>

<script type="text/javascript">
  $(function() {
  $('#DateOfBirth').datepicker({ dateFormat: 'yy/mm/dd' });
  });
  $('#save').click(function () {
      $.ajax({
          type: "POST",
          url: $("#AddContact").attr('action'),
          data: $("#AddContact").serialize(),
          dataType: "text/plain",
          success: function (response) {
              if (response == "Saved") {
                  window.location = "/"; 
              }else {
                  $("#details").html(response);
              }
          }
      });
  });
</script>

<% using (Html.BeginForm("Add", "Contact", FormMethod.Post, new { id = "AddContact" }))
   {%>
      <table>
        <tr>
            <td class="LabelCell"><%= Html.LabelFor(m => m.FirstName)%> </td>
            <td><%= Html.TextBox(m => m.FirstName)%> 
            <%= Html.ValidationMessageFor(m => m.FirstName)%></td>  
        </tr>
        <tr>
            <td class="LabelCell"><%= Html.LabelFor(m => m.MiddleName)%> </td>
            <td><%= Html.TextBox(m => m.MiddleName)%></td>  
        </tr>
        <tr>
            <td class="LabelCell"><%= Html.LabelFor(m => m.LastName)%> </td>
            <td><%= Html.TextBox(m => m.LastName)%> 
            <%= Html.ValidationMessageFor(m => m.LastName)%></td>  
        </tr>
        <tr>
            <td class="LabelCell"><%= Html.LabelFor(m => m.DateOfBirth)%> </td>
            <td><%= Html.TextBox(m => m.DateOfBirth)%> 
            <%= Html.ValidationMessageFor(m => m.DateOfBirth)%></td>  
        </tr>
        <tr>
          <td class="LabelCell"><%= Html.LabelFor(m => m.Type)%></td>
          <td><%= Html.DropDownList("Type")%>
          </td>
        </tr>
        <tr>
          <td class="LabelCell"></td>
          <td><input type="button" name="save" id="save" value="Save" /></td>
        </tr>
      </table>
<% } %>

 

可以看出,这里使用新的强类型Html Helper.对前面项目修改的两处是利用了jQuery代码。第一处是添加联系人的Partial View是通过AJax提交,如果验证失败,则添加的form会再次被显示,如果验证通过,新的联系人被添加到列表中,页面会刷新继而显示更新后包含新联系人的列表。下面来看如果不输入任何日期时点击Save按钮的效果:


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

 

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Add([Bind(Exclude = "Id, Type")]ContactPersonViewModel person)
{

    if (ModelState.IsValid)
    {
        var p = new ContactPerson
        {
            FirstName = person.FirstName,
            MiddleName = person.MiddleName,
            LastName = person.LastName,
            Type = Request.Form["Type"].ParseEnum<PersonType>()
        };
        if (person.DateOfBirth != null)
            p.DateOfBirth = (DateTime)person.DateOfBirth;
        ContactPersonManager.Save(p);
        return Content("Saved");
    }
    var personTypes = Enum.GetValues(typeof(PersonType))
    .Cast<PersonType>()
    .Select(p => new
    {
        ID = p,
        Name = p.ToString()
    });
    person.Type = new SelectList(personTypes, "ID", "Name");
    return PartialView(person);
}
 

在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 文中)如下:

 

public static T ParseEnum<T>(this string token)
{
    return (T)Enum.Parse(typeof(T), token);
}
 

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

 

<tr>
  <td class="LabelCell"><%= Html.LabelFor(m => m.DateOfBirth)%> </td>
  <td><%= Html.EditorFor(m => m.DateOfBirth)%> 
      <%= Html.ValidationMessageFor(m => m.DateOfBirth)%></td>  
</tr>
 

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


而DateTime.ascx的代码如下:

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

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

 

总结
 

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

 

联系人程序在添加了额外的验证功能后进一步升级,但离完成还遥遥无期,在未来的文章中,我会继续更新这个程序。

 

代码可以点击这里下载

 

------------------------------

原文链接:Validating the Contact Manager with MVC 2.0 And VS 2010

translated byCareySon




本文转自CareySon博客园博客,原文链接http://www.cnblogs.com/CareySon/archive/2010/04/07/1706512.html如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
windows下vs2013使用C++访问redis
刚开始在windows下使用c++访问reids各种报错,经过网上到处搜方案,终于可以在windows下访问redis了,特将注意事项记录下来: 1.获取redis Window下的开发库源码,从github获取windows版:https://github.
1092 0
Serverless Kubernetes全面升级2.0架构:支持多命名空间、RBAC、CRD、PV/PVC等功能
阿里云Serverless Kubernetes容器服务最新开放香港、新加坡、悉尼区域,同时全面开放2.0架构,帮助用户更加便捷、轻松地步入“以应用为中心”的全新架构。
3051 0
c++ 使用vs2010调用 win32api
以前读书时都是用vc6.0.后来学c#用vs。装系统只装了vs2010.今天用vs2010写c++程序。发现有点陌生。就总结下,免得以后忘记。   首先用vs2010选择c++语言。新建一个win32控制台程序。
847 0
使用MVCPager进行博客园首页列表数据的分页显示功能
在前一篇博客中使用正则表达式抓取博客园列表数据,我通过正则表达式抓取了博客园的部分数据作为测试数据,现在测试数据也有了,就应该进行数据的分页显示了。 但是如何分页这倒是让我犹豫了好几分钟,是自己写javascript来自定义分页显示,还是通过现成的控件来进行分页,通过自定义分页可以完全的对分页进行控制,但是会很耗费时间,这对于js功能很差的我就是个难题,但是为了尽快的能实现这个分页功能,我依然采用了分页控件进行分页,如果以后有机会我再手动写一个分页js来进行分页。
960 0
git在vs2017中的使用
    对于习惯了右键提交源代码的道友来说,敲命令行真的蓝瘦香菇。所幸17里集成了Git插件,用起来还是挺方便的。     1.本地安装git,工具还是要有的,主要用于配置环境,ssh配置一下。就不用每次都去连接了。
1816 0
+关注
3656
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载