ASP.NET MVC4 IN ACTION学习笔记-第五波

ASP.NET MVC4 IN ACTION –Validation

--每个人的都有潜力,不要做被别人看不起的人,You are the best!

 

image原著:ASP.NET MVC 4 IN ACTION

本人能力有限,尽量将书中的知识浓缩去讲,仔细学过后,然后你再学习其他语言的MVC框架也就大同小异了

本次覆盖知识点:

  • 1. 实现Data Annotations(Implementing Data Annotations )
  • 2. 拓展ModelMetadataProvider (Extending the ModelMetadataProvider)
  • 3. 使用客户端验证 (Enabling client-side validation )
  • 4. 创建一个自定义的客户端验证器 (Creating custom client side validators )
 

与读者的约定:

  1.关于rich这个单词,在本篇有几处说到一些,为了是博客更好理解,在这里说明一下,以方便本文读起来更顺口。

  例如:本波主要讲验证,所以rich experience 丰富的体验,代表,很复杂的表单,比如qq的注册页面,邮箱啊,身份证啊等等,且验证也很漂亮,我们就说这种体验叫rich experience,所以rich在本我的博客里面的意思是,用来形容某样东西,很好的,用起来会很爽的意思。

  2. validator:验证者,在我的博客里,你就理解成,它是一类验证的工具,比如 email validator验证email的validator,integer validator验证数字的validator

  3. validation attribute:在服务端验证的时候我们都会使用在model上的属性上加上attribute(特性)来约束属性值的合法性,验证其合法性,关于validation attribute也就是validation时用到的一些attribute。

  4.Unobtrusive JavaScript:简单地来说,就是一种代码分离的思想,把行为层和表现层分离开

 

在前面几波,我们都在讲Model,在本波里面我们还是研究MVC中的M部分,这次我们看一下更复杂的一些环境。在MVC框架里面,提供了一些rich user input validation(用户输入后验证的效果更rich)。Validation很重要,是MVC中的一大特色,为了让用户输入数据在传入我们的数据库中,格式符合我们要求,所以我们要加验证。

在MVC的第一个版本中,框架中是没有Validation的,它集成了一些第三方验证框架,很难用,难用体现在,不能扩展,在ASP.NET MVC2中,Validation就已经很完美的支持了,它内置了Microsoft的Data Annotations库,在MVC3中Validation又有提升,提升了客户端验证的环节,已经绰绰有余地满足了当今的Web应用程序。

几乎每个应用程序都有登陆页面的场景,那是很简单的验证,过会我们写一个。在本波博客里,我们主要研究一下在Data Annotations库中提供的一些内置的validator,接下来,我们拓展model里面的metadata provider,使得用法更好,model中的行为更rich。最后,我们看下客户端的验证,因为这是给当今网站访问者最快的反馈手段,能够给用户rich experience,它能更好地满足当今开发者的需求和用户的需求。

 

6.1 服务端验证(Server-side Validation)

      不管客户端是否验证了,服务端验证还是需要的。用户可以禁用Javascript,也可以通过你想不到的手段绕过验证,所以服务器端的验证是防止dirty input(用户输入的传到服务器的脏数据)的最后一道防线了。有些validation rules(规律,规则)要求server端去处理--network topology(网络位相学)可能是这种情况:只有server可以访问外部的一些资源(比如js文件)去validate input(验证用户输入的数据)

      我们要理解两个关键的思路:使用Data Annotations常用的方式去验证Server端的验证;然后我们去investigate(研究)model metadata,再然后我们去写一个自定义的provider

 

6.1.1 用Data Annotations方式去Validation

      Data Annotations是在.NET 3.5 SP1 介绍到,在System.ComponentModel.DataAnnotations程序集里,是个attributes和类的集合,它允许你使用metadata去修饰你的类。这些metadata描述了一组约定(rules),然后你的object就可以被验证了。

DataAnnotation的attributes控制了很多的验证。比如一些新模版的验证,就像我们在第二波介绍到的DisplayName,DataType特性,这些特性,在表6-1列举出来了。ASP.NET MVC包括了一个与每个attribute相关的支持验证的类的集合,让验证更出色。

      为了证明 validation attribute的使用,让我们看一下可能用到的验证,图6-1是一个Edit 页面,包含了CompanyName 和    Email Address属性,由于这些Attribute简单,所以后面的描述我就不翻译了。

image

image

图6-1

 

新建项目ch5_1,无需添加单元测试项目

image

在Models文件夹中添加CompanyInput.cs

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel.DataAnnotations;
   4:  using System.Linq;
   5:  using System.Web;
   6:   
   7:  namespace Ch5_1.Models
   8:  {
   9:      public class CompanyInput
  10:      {
  11:          [Required]
  12:          public string CompanyName { get; set; }
  13:   
  14:          [DataType(DataType.EmailAddress)]
  15:          public string EmailAddress { get; set; }
  16:      }
  17:   
  18:  }

CompanyName使用了RequiredAttribute,EmailAddress使用了DataTypeAttribute(采取了Email地址验证模版)

在view中,我们需要展示验证失败的错误信息,我们有很多种方法完成,如果我们使用model模版,验证的信息就已经在模版中了。

打开HomeController添加一个Edit action

   1:        public ActionResult Edit()
   2:          {
   3:              return View();
   4:          }

并添加对应的视图,Edit.cshtml

image

默认的editor model模版会生成user interface(一个又一个input节点和验证信息)

为了更好地控制output,我们可以使用HtmlHelper验证的拓展方法,ValidationSummary拓展提供了一个验证错误的信息的总结列表,通常展示在form的上面,为了是特定的验证的错误信息对应指定的model属性,我们可以使用ValidationMessage或者基于表达式的ValidationMessageFor方法。

在写好validation message信息,一切准备就绪之后,我们需要在我们的controller中的action中审核我们的model的Valid属性

image

我们使用ModelState.IsValid判断是否验证通过,通过后,MVC验证引擎会把validation errors放进ModelState里面,然后把错误的信息的是否存在放在IsValid属性里,如果没有错误,我们将会跳转到Success view页面,否则,会显示错误。

按下F5运行项目,直接点击submit

image图6-2

在图6-2中,在验证的错误的信息上还有一些问题,”CompanyName”中间没有空格,我们希望有个空格,一种是我们修复label,包括DisplayNameAttribute(在System.Component空间中),但是因为在单词之间添加一个空格是有必要的,下面我们拓展ModelMetadataProvider类让它自动有空格

 

======本波博客来自茗洋芳竹所有,任何人未经允许不得转载:http://www.cnblogs.com/AaronYang======

 

6.1.2 拓展ModelMetadataProvider

在前面几节我们都看到了ASP.NET MVC中的model metadata,使用model metadata的展示的input element和文本,validation providers使用model metadata去执行验证的模版

如果我们想要我们的model metadata(元数据)从sources赋值,而不是Data Annotations的时候,我们就需要从ModelMetadataProvider开始下手。

ModelMetadataProvider 类包含了一些方法:在类型中的每一个成员的ModelMetadata的集合,能够指定属性的获得ModelMetadata的方法,能够根据指定类型获得ModelMetadata,所有的这些可以在列表6-1看到

image

为了能够达到自定义指定属性的展示的文本的形式,我们需要重写DataAnnotationsModelMetadataProvider类

在我们的例子里,我们需要修改DisplayName model metadata的行为,默认的ModelMetadata的DisplayName属性来自DisplayNameAttribute。我们仍然通过Attribute来补充DisplayName值

在下面代码中,我们拓展了内置的DataAnnotationsModelMetadataProvider 来,通过属性名称来分离成单词的方式来创造 DisplayName最终显示的样子

我们在Models中创建一个继承DataAnnotationsModelMetadataProvider的ConventionProvider.cs文件

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text.RegularExpressions;
   5:  using System.Web;
   6:  using System.Web.Mvc;
   7:   
   8:  namespace Ch5_1.Models
   9:  {
  10:      public class ConventionProvider : DataAnnotationsModelMetadataProvider
  11:      {
  12:          protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
  13:          {
  14:              var meta = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
  15:              if (meta.DisplayName == null)
  16:              {
  17:                  meta.DisplayName = meta.PropertyName.ToSeparatedWords();
  18:              }
  19:              return meta;
  20:          }
  21:      }
  22:      public static class StringExtensions
  23:      {
  24:          public static string ToSeparatedWords(this string value)
  25:          {
  26:              if (value != null)
  27:                  return Regex.Replace(value, "([A-Z][a-z]?)", " $1").Trim();
  28:              return value;
  29:          }
  30:      }
  31:  }

ToSeparatedword是一个根据Pascal命名法分隔单词,例如CompanyNameWa会变成 Company Name Wa的形式

当我们自定义的ModelMetadataProvider构建好后,我们需要让我们的ASP.NET MVC框架去使用我们的新provider。在哪里初始化声明呢?这最常见的位置就是Global.asax文件中了

image

按下F5运行,点提交,查看错误状态,Display模版都有空格了,按照Pascal方式断词了

image

到目前位置,这个例子中,都是服务器端的验证,但是ASP.NET MVC提供了分离的客户端和服务器端验证的支持,接下来我们看看客户端的验证

 

6.2 客户端验证(Client-side validation)

     随着更多时髦的浏览器,还有rich Client的到来,在form中,使用Javascript去验证已经成为主要手段。客户端验证的反馈比服务端的反馈更快,因为服务端验证,从客户到服务端的一次轮询是不可避免的,许多客户端验证框架包括了高级的功能:比如当input element失去焦点的时候开始验证,所以用户在tapping(敲击键盘)的时候,当离开表单的时候就会获得动态的验证信息。

    西拼乱凑绑定验证行为是浪费时间和成本过高的。因为许多客户端的验证框架在开发的时候,还有产品发行这几年已经完成了,基本完美了。所以没必要写重复的代码了,在ASP.NET MVC中,这种重复的劳动已经大大的减少了。

    ASP.NET MVC捆绑了用于客户端验证的Jquery Validate库的支持,另一个新的特色是支持unobtrusive客户端验证,这是一种在呈现的input element上使用data特性的一种脚本引用,验证脚本会监测这个节点,并相应的给出反应,在ASP.NET MVC2中,客户端验证是不突出的,也就是一段特殊的脚本跟着input节点,然后与input建立关联

    在下一节中,我们将要研究在ASP.NET MVC中的客户端验证,建立一个基本的项目,研究自定义规则的两个点—RemoteAttribute,创建一个自定义的JQuery validators

 

6.2.1 开始客户端验证

开始学习客户端验证,首先在shared文件下的_Layout.cshtml文件中添加脚本。

image

   1:   <script src="@Url.Content("~/Scripts/jquery-1.8.2.min.js")" 
   2:            type="text/javascript"></script>
   3:      <script src="@Url.Content("~/Scripts/jquery.validate.js")" 
   4:            type="text/javascript"></script>
   5:      <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" 
   6:            type="text/javascript"></script>

每一个Javascript库都是建立在上一个Javascript基础上的,所以这3个js文件的放置顺序很重要,我们首先注册Jquery库,不久注册JQuery插件,然后就是unobtrusive验证。

把这些客户端库包含在母版页里,我们就可以有选择地进行 unobtrusive客户端验证了。这一点可以在应用程序级别的web.config中完成,或者在每一个请求的页面上加上两个方法。

  imageimage

关于那两个方法必须放在BeginForm前面,如上那两段代码表示这两个脚本启用,然后就可以在页面上这样写代码了,就可以自动验证

image

这些metadata来自jquery.unobtrusive JavaScript库,还有JQuery Validate 插件验证的逻辑

由于我们服务端的验证还在,我们完全放下心来,即使浏览器禁止掉Javascript,也还是能启动验证的逻辑的,ASP.NET MVC也支持自定义的validator,一个插件,为客户端和服务器端准备的。

 

======本波博客来自茗洋芳竹所有,任何人未经允许不得转载:http://www.cnblogs.com/AaronYang======

 

6.2.2 RemoteAttribute的使用

      在ASP.NET MVC3中有了一个新的validation特性就是RemoteAttribute。修饰了一个model属性,通知Jquery验证发送一个Http请求给一个action方法做一次服务器端验证,然后处理结果会返回给客户端,然后一个错误信息就会在表单提交之前显示。这是个很好的方式,提供了一个rich client experience(富客户端体验)的很好的一个服务器端验证的方法。

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel.DataAnnotations;
   4:  using System.Linq;
   5:  using System.Web;
   6:  using System.Web.Mvc;
   7:   
   8:  namespace Ch5_1.Models
   9:  {
  10:      public class UsingRemote
  11:      {
  12:          [Required]
  13:          [Remote("IsNumberEven", "Home", ErrorMessage = "数字必须是偶数!")]
  14:          public int EvenNumber { get; set; }
  15:      }
  16:  }

这个特性表明了哪一个controller,哪一个action,客户端脚本将会调用,它也指定了一个错误的信息,在用户改变节点(element)值的时候,客户端脚本将会发送name和value给action,这个action名字参数必须和input element的名字一致,匹配

   1:      public JsonResult IsNumberEven(int evenNumber)
   2:          {
   3:              return Json(evenNumber % 2 == 0,
   4:                   JsonRequestBehavior.AllowGet);
   5:          } 

这个action的作用是,验证这个数字是不是偶数,并且返回一个Boolean型的,然后被Json方法包裹了,这个Boolean决定了是否成功,true当然就是意味着验证成功。在这种情况下,当用户在填写form的时候,用户要承受一点点HTTP请求,RemoteAttribute是一个很简单就能丰富客户端体验的一个很好的方式,如果你更关心性能,很多这些请求都可以转换成一个自定义的客户端验证。

开始练习:

我们首先在Home控制器中,添一个action

   1:         public ActionResult RemoteAttribute(UsingRemote input)
   2:          {
   3:              return View(input);
   4:          }

添加对应的视图:

   1:  @model Ch5_1.Models.UsingRemote
   2:   
   3:  @{
   4:      ViewBag.Title = "Using RemoteAttribute";
   5:  }
   6:   
   7:  <h2>Edit</h2>
   8:   
   9:  @using (Html.BeginForm("IsNumberEven", "Home")) {
  10:      
  11:      @Html.EditorForModel()
  12:      <button type="submit">Submit</button>
  13:  }

运行效果图如下:

image挑一个不是偶数的开始测试

image我发现页面没有刷新,异步的

所以Remote特性是一个,特殊单个属性可以使用特定的controller action去处理这个单个属性.

 

======本波博客来自茗洋芳竹所有,任何人未经允许不得转载:http://www.cnblogs.com/AaronYang======

 

6.2.3 创建一个自定义的客户端Validator

       当一个验证中的特性实现了IClientValidatable,DataAnnotationsModelMetadataProvider(还有很多的衍生,比如ConventionProvider),它就可以通知框架把这些数据特性和HTMl中的element(DOM节点)关联起来。使用这个机制,我们可以实现自定义的客户端验证,使用我们自己的JavaScript代码去验证,当JQuery验证库提供的validator不够用时候,这个是对特定的应用程序行为是有用的。

       在下面一个例子里,我将要添加一个验证逻辑去确保一个表单上的时间是晚于其他的时间的。用户不想在一个错误的需求上输入日期,我们给他们在提交表单之前提供一个更快的选择的方式,IClientValidatable接口有一个方法,它提供了自定义validator的metadata

   1:     public interface IClientValidatable 
   2:            { 
   3:              IEnumerable<ModelClientValidationRule> GetClientValidationRules( 
   4:                 ModelMetadata metadata, ControllerContext context); 
   5:            } 

最终效果图:结束时间要小于开始时间

image

 

这个方法接受了一个model metadata参数,所以在我们验证的时候,我们可以为我们的指定的model属性自定义一个验证规则,在我们的例子中,我们使用格式化后Displayname(比如原来是CompanyName我们格式化后是Company Name)去生成一个错误的信息,为了确保最大的拓展性,这个方法返回了一个ModelClientValidationRule泛型的IEnumerable集合,但是它返回了一个约束的集合还是好的,在我们遇到所有的例子中,一个一个关联都是有意义的。IClientValidatable 接口允许 ASP.NET MVC 在运行时发现支持的客户端验证器,这个接口被用来支持集成不同的验证框架.

实现的代码如下:

①在Models文件夹新建一个DateComesLaterAttribute.cs类,让它继承ValidationAttribute,实现我们自己的名称的特性,实现后,重写IsValid方法,这里的属性过会就是特性里面的参数了

   1:          public class DateComesLaterAttribute : ValidationAttribute
   2:          {
   3:              public const string DefaultErrorMessage = "'{0}' must be after '{1}'";
   4:   
   5:              protected readonly string OtherDateProperty;
   6:              /// <summary>
   7:              /// 构造函数
   8:              /// </summary>
   9:              /// <param name="otherDateProperty">其他日期属性</param>
  10:              public DateComesLaterAttribute(string otherDateProperty)
  11:              {
  12:                  this.OtherDateProperty = otherDateProperty;
  13:              }
  14:   
  15:              protected override ValidationResult IsValid(object value, ValidationContext validationContext)
  16:              {
  17:                  object instance = validationContext.ObjectInstance;
  18:                  Type type = validationContext.ObjectType;
  19:   
  20:                  var earlierDate = (DateTime?)type.GetProperty(OtherDateProperty).GetValue(instance, null);
  21:                  var date = (DateTime?)value;
  22:   
  23:                  if (date > earlierDate)
  24:                      return ValidationResult.Success;
  25:   
  26:                  string errorMessage = GetErrorMessage(validationContext.ObjectType, validationContext.DisplayName);
  27:   
  28:                  return new ValidationResult(errorMessage);
  29:              }
  30:   
  31:              protected string GetErrorMessage(Type containerType, string displayName)
  32:              {
  33:                  ModelMetadata metadata = ModelMetadataProviders.Current.GetMetadataForProperty(null, containerType,
  34:                                                                                                 OtherDateProperty);
  35:                  var otherDisplayName = metadata.GetDisplayName();
  36:                  return ErrorMessage ?? string.Format(DefaultErrorMessage, displayName, otherDisplayName);
  37:              }

为了让DateComesLaterAttribute 验证属性也能在客户端进行验证,我们索性就再写一个特性,实现IClientValidatable接口

   1:      public class DateComesLaterClientAttribute : DateComesLaterAttribute, IClientValidatable
   2:      {
   3:          public DateComesLaterClientAttribute(string otherDateProperty) : base(otherDateProperty) { }
   4:   
   5:          public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,
   6:                                                                                 ControllerContext context)
   7:          {
   8:              var rule = new ModelClientValidationRule
   9:              {
  10:                  ErrorMessage = GetErrorMessage(metadata.ContainerType, metadata.GetDisplayName()),
  11:                  ValidationType = "later",
  12:              };
  13:   
  14:              rule.ValidationParameters.Add("other", "*." + OtherDateProperty);
  15:   
  16:              yield return rule;
  17:          }
  18:      }

 

 

 

IClientValidatable 接口很简单,他只有一个GetClientValidationRules()方法,我们只要实现这个方法就可以了,这个方法包括两个参数,metadata表示要验证的属性的元数据,context表示发送请求的Controller的上下文,并且它返回一个ModelClientValidationRule集合。

ModelClientValidationRule 是一个只有3个属性的很简单的一个类.验证失败后,ErrorMessage就会显示出来,ValidateType是validator的名字,ValidationParameters是一个IDictionary<string,object>-一组可以通过客户端脚本验证的参数.在上面代码中,我们设置的错误信息是基于displayname的.这里ValidateType是"later",这个是我们过会将要写的JQuery Validator的名字.我们还要添加一个其他的数据属性,就是我们将要跟它比较的,参考的一个对象.

实现了IClientValidatable后,ASP.NET MVC就会使用JQuery Validate为unobtrusive client validation呈现正确的特性,两个步骤:①写一个JQuery Validate validator ②使用unobtrusive特性和validator关联起来

我们过会将使用一个很简短的JavaScript代码来添加一个validator,这个validator的作用就是保证它的日期比别的日期element都新,我们使用JavaScript的Date对象来做比较.value参数是我们将要验证的值,params参数是其他input element(我们在验证特性中定义的)

 

我们打开Scripts文件夹下的jquery.validate.unobtrusive.js文件

image

然后我们,继续往下面查看代码,会发现:

image

接下来,我们在这块添加如下代码(可添可不添加)

   1:  adapters.add("later", ["other"], function (options) { 
   2:          var prefix = getModelPrefix(options.element.name), 
   3:          other = options.params.other, 
   4:          fullOtherName = appendModelPrefix(other, prefix), 
   5:         element = $(options.form).find(":input[name=" + fullOtherName + "]")[0]; 
   6:          setValidationValues(options, "later", element); 
   7:      });

image

添加这行代码,我们就可以在客户端的用unobstrusive JavaScript方式验证,我们先采用server端方式

接下来我们在Models文件夹中新建一个ClientCustomValidation.cs

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel.DataAnnotations;
   4:  using System.Linq;
   5:  using System.Web;
   6:   
   7:  namespace Ch5_1.Models
   8:  {
   9:      public class ClientCustomValidation
  10:      {
  11:          [DataType(DataType.Date)]
  12:          [Required]
  13:          public DateTime? BeginDate { get; set; }
  14:   
  15:          [DataType(DataType.Date)]
  16:          [DateComesLaterClient("BeginDate")]
  17:          public DateTime? EndDate { get; set; }
  18:      }
  19:  }

 

在Home控制器中,添加一个action,和对应的view

   1:       public ActionResult ClientCustomValidation() {
   2:   
   3:              return View(new ClientCustomValidation());
   4:          }
   5:   
   6:          [HttpPost]
   7:          public ActionResult ClientCustomValidation(ClientCustomValidation input)
   8:          {
   9:              if (ModelState.IsValid)
  10:              {
  11:                  return View("Success");
  12:              }
  13:              return View(new ClientCustomValidation());
  14:          }

view中:

   1:  @model Ch5_1.Models.ClientCustomValidation
   2:   
   3:  @{
   4:      ViewBag.Title = "ClientCustomValidation";
   5:  }
   6:   
   7:  <h2>验证日期Demo</h2>
   8:   
   9:  @using (Html.BeginForm("ClientCustomValidation", "Home"))
  10:  {
  11:    @Html.EditorForModel()
  12:    <button type="submit">Submit</button>
  13:  }

效果图:

image

 

关于 client端的这种验证,我没有去做,这个例子,只是说明如何自定义一个 验证特性,然后使用它.

书上讲的 省略死我了,简直在虐我,我东查资料,西查资料完成的.

看我这么辛苦的份上,给个推荐吧,让大家知道我这个ASP.NET MVC4 IN ACTION系列文章,让更多的人可以更快的入门MVC

谢谢你们的支持!

 

关于下一波博客,我打算分两波的博客讲解,MVC中的AJAX方面的,内容很多,估计是本波博客的2倍的量,好有压力~~o(>_<)o ~~

 

 

关于ASP.NET MVC4 IN ACTION系列目录地址已经生成:点击查看目录

 本波博客代码: 点击下载

posted @ 2013-04-24 23:45  AYUI框架  阅读(2925)  评论(8编辑  收藏  举报