ASP.NET MVC---内置的数据注解

       验证是ASP.NET MVC开发中一个非常重要的环节,包括客户端和服务端验证。幸好,MVC提供了非常简便的数据注解(Data Annotations)来帮助我们进行这项工作。

1.验证性的数据注解

       MVC本身内置了一些常用的数据注解,像是Required,DisplayName等等,我会在下面一一讲解。

       最常用的就是Required,像是下面这样:

     

      使用Required可以指定错误消息:

[Required(ErrorMessage = "First Name is required")]
public string FirstName { get; set; }

      使用Required是不够的,我们还需要规定用户的输入限制,像是字符串,就常有长度的限制,这时我们就可以利用StringLength。像是这样:

[Required(ErrorMessage = "First Name is required")]
[StringLength(160)]
public string FirstName{ get; set;}

      它规定了我们输入的最大长度是160个字符。如果想要规定最小长度,我们可以这样写:

[Required(ErrorMessage = "First Name is required")]
[StringLength(160, MinimumLength = 3)]
public string FirstName { get; set; }

      实际的效果如图:


      字符串的要求是长度,而数字的要求则是数据范围范围:

 [Range(0.01, 100.0, ErrorMessage = "Price must be between 0.01 and 100.0")]
 public decimal Price { get; set; }

      Range()是一个双闭区间,就是说,包含0.01和100.0。

      最后一个要讲的,就是RegularExpression。

     我们可以这样使用:

[Required(ErrorMessage = "Email is required")]
[RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+[A-Za-z]{2,4}", ErrorMessage = "Email is not valid")]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }

    通过上面,我们可以知道,所有用于验证用的数据注解都可以自定义自己的错误消息。

2.显示性的数据注解

      我们都注意到,变量FirstName在页面上的实际显示是:First Name。这样做才是符合实际需要的,要想这样做,我们就必须利用DisplayName:

[Required(ErrorMessage = "First Name is required")]
[DisplayName("First Name")]
[StringLength(160, MinimumLength = 3)]
public string FirstName { get; set; }

     同样是为属性定义说明,还有另一种注解:Dispaly,它的作用并不仅仅是指定显示的内容,甚至能够指定显示的顺序,像是这样:

[Required(ErrorMessage = "First Name is required")]
[Display(Name = "First Name", order = 15000)]
[StringLength(160)]
public string FirstName { get; set; }

       order的默认参数是10000,如果其他变量并没有设置order值,FirstName就排在其他变量后面。但实际上是不用这么麻烦的,如果不指定该值,就会按照变量声明的顺序排列。

      Display在用于属性的显示名称上,具有更高的优先级。

      DisplayName专门用于定义属性的显示名称,但是Display并不仅仅是如此。它的内容非常丰富,有些在这里不好讲,而且本人是MVC新手,所以还请感兴趣的同学自己查一下相关资料。

      如果对数据库有所了解的话,就会知道,数据库需要key值用于搜索相应的元组。MVC经常与数据库打交道,所以我们的模型中经常需要定义一个或几个名称中包含有Id的作为key的属性,像是OrderId之类的,但是我们在显示的时候又不想将这些属性显示出来,但正如我上面所讲的,默认是会将该模型的所有属性都显示出来。那该怎么办呢?就是对显示隐藏,我们可以使用HiddenInput。

      像是这样:

public class Person{
    [HiddenInput]
    public string Name { get; set; }

    [HiddenInput(DisplayValue = false)]
    public string Sex { get; set; }

    public int Age { get; set; }
}

      然后是控制器:

public ActionResult Index(){
     Person person = new Person() { Name = "Jos", Sex = "man", Age = 18 };
     return View(person);
}

      实际的效果如图:
      

       我们可以看到,"Sex"完全被隐藏起来了!

       使用了HiddenInput,默认情况下属性会以只读形式显示出来,像是上面的Name,要想完全隐藏,就得将DisplayValue设置为false。

       我们可以来看看使用了HiddenInput的HTML:

<div class = "editor-label"><label for = "Name">Name</label></div>
<div class = "editor-label">Jos<input id = "Name" name = "Name" type = "hidden" value = "Jos"/></div>

<input id = "Sex" name = "Sex" type = "hidden" value = "man"/>

<div class = "editor-label"><label for = "Age">Age</label></div>
<div class = "editor-field"?
    <input class = "text-box single-line" id = "Age" name = "Age" type = "text" value = "18"/>
<div>

      这些只要了解就好,MVC会自动帮我们处理。
      我们有时候会想,这样做是好的吗?毕竟,MVC会帮我们自动生成HTML文件,包括一些本该是由程序员自己设置的标记。但必须承认,这样让程序员从千篇一律的编码中解放出来,让我们能够将关注点放在其他更值得关注的方面上,而且,我们程序员依然对生成的HTML文件具有很大的控制权。

      特别强调一下,HiddenInput的命名空间是System.Web.Mvc。

      同样是隐藏属性在HTML上的显示,我们还可以使用ScaffoldColumn特性。

      使用ScaffoldColumn并不是为属性设置type = "hidden",它是直接将该属性从基架中删除。我们知道,MVC可以通过预定义模板来自动生成HTML,这种方式就是基架(Scaffolding)。ScaffoldColumn表示存在于基架中并最终呈现在HTML中的字典。

      像是这样:

[ScaffoldColumn(false)]
public int OrderId { get; set; }

     即使从基架中将该属性删除,模型绑定器仍然会试图为该属性赋值,这样就为典型的攻击“重复提交”提供了机会。黑客们可以试图向我们的网页中发送"OrderId = 100"这样的字段,而我们的模型绑定器会为该属性赋值并且有可能赋为黑客提供的值。要想防止这种攻击,我们可以利用Bind特性。
     Bind特性可以选择模型绑定器要绑定的值。像是这样:

[Bind(Include = "Name, Comment")]
public class Review{
     public int ReviewId{ get; set;}
     public int ProductId{ get; set;}
     publc string Name{ get; set;}
     public string Comment{ get; set;}
}

      这样模型绑定器就只绑定Name和Comment属性。
      当然,我们也可以选择不绑定的属性:

[Bind(Exclude = "OrderId")]
    public partial class Order
    {
        [ScaffoldColumn(false)]
        public int OrderId { get; set; }
        
        [ScaffoldColumn(false)]
        public string UserName { get; set; }

     }

     这样,上面所讲的“重复提交”攻击就无法发挥作用了。但是必须注意,使用"Include"的白名单比起使用"Exclude"的黑名单更加安全,因为我们永远也不知道黑客会用怎样的方式来攻击我们,但确定的是,我们可以知道哪些被绑定的属性是不会造成太大的危害的。

      使用白名单还是黑名单还是得看具体情况,毕竟有时候我们需要绑定的属性非常多,如果采用白名单的方式,光是数据注解这里可能就要超过了我们该模型类的代码总量了。

      Bind既可以用于模型,也可以用于控制器操作的参数,但更多时候还是用在模型上。

      提到模型绑定器,这里就必须得说一下属性的更新问题。模型绑定器会更新被绑定的属性的值,但有些值我们可能希望它永远都不要被更新,那么我们就可以使用ReadOnly特性。

      像是这样:

[ReadOnly(true)]
public decimal Total{ get; set;}

      模型绑定器就不会更新它的值,虽然实际运行的时候,我们的确可以在文本框里修改它的值,但是该值并不会被用来更新该属性的值。

      ReadOnly的命名空间是在using System.ComponentModel。

      既然提到可读,那么一定存在可写。是的,在System.ComponentModel.DataAnnotations中就有一个Editable。

      它的使用方式和ReadOnly完全一样,但我们会很好奇:如果同时将这两个注解用在同一个属性上,会怎么样呢?事实上,Editable拥有更高的优先级。
      接下来讲到的数据注解就真的非常重要。所有显示性的数据注解,其实都是提供给HTML 辅助方法和ASP.NET MVC运行时的其他组件使用,这些都是靠模型元数据提供器来负责收集。所以,就有一种数据注解能够为运行时提供关于属性的特定用途。这就是DataType。

      我们会在用户的登陆界面中要求用户输入登陆密码,但是又必须保证,这些密码不会显示出来以免被别人看到,这时,DataType就发挥作用了:

[Required]
[DataType(DataType.Password)]
[DisplayName("Password")]
public string Password { get; set; }

      从上面可以看出,DataType所谓的数据类型,并不是我们常说的int,float等,事实上,DataType本身也有一个可支持的数据类型枚举:

public enum DataType{
    Custom, DateTime, Date, Time, Duration, 
PhoneNumber, Currency, Text, Html, MultilineText,
EmailAddress, Password, Url, ImageUrl, CreditCard,
PostalCode, Upload }

     DataType实际上是一个验证特性,它继承自ValidationAttribute,但我之所以放在这里讲,是因为所谓的"显示"性,是指它的作用效果,像DataType的作用效果就是为它内置的数据类型提供不同的显示效果。
     当然,我们还可以自定义自己的数据类型,不过这种情况下我们就必须自定义该数据类型的DisplayFormat。这些都在DataType的源码中:

[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method, AllowMultiple=false)]  public class DataTypeAttribute : ValidationAttribute{
    public DataTypeAttribute(DataType dataType); 
    public DataTypeAttribute(string customDataType);
    public virtual string GetDataTypeName();
    public override bool IsValid(object value);
    public string CustomDataType { get; }
    public DataType DataType { get;  }
    public DisplayFormatAttribute DisplayFormat { get; }
}

      我们有时候想要对输出进行格式化设置,这时我们就可以利用DisplayFormat。

      像是这样:

[Required(ErrorMessage = "Price is required")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:c}")]
[Range(0.01, 100.0, ErrorMessage = "Price must be between 0.01 and 100.0")]
public decimal Price { get; set; }

      就会有这样的效果:    

 

      话说,中文版的VS2012莫非真的是入乡随俗,我看到外国的教程都是美元标记,但这里却是RMB标记!但这点确实真的很重要,毕竟,在中国发布的网站价格标注的要是美元的话,那还真的是没有多少人会买了。

      这里必须注意,ApplyFormatInEditMode的默认值是false,之所以这样,是因为我们模型绑定器无法解析格式化的值。

      查看DisplayFormat的源码,我们就可以发现,它还有两个个布尔类型的属性:HtmlEncode和ConvertEmptyStringToNull,前者表示是否需要对目标内容进行HTML编码,默认下是true(这样是为了安全性的考虑),后者表示是否将传入的空字符串转换成Null。

      DisplayFormat还有一个string属性:NullDisplayText,它表示针对空值(Null)对象的显示文本。

      同样的道理,因为DataType也对应着一个DisplayFormatAttribute,所以当这两个特性同时应用在相同的元素上,后者具有更高级的优先级。

      最后一个要讲的数据注解,因为不知道要放在哪里,但提到模板的话,就还是放在这里好了。它就是UIHintAttribute。

      我们知道,HTML辅助方法会帮我们选择基于Model的模板方法。所谓的模板方法,指的是我们在通过调用这些方法来将Model的数据显示在View中时,采用默认或者指定的模板来决定最终呈现在浏览器中的HTML。这点在我们使用MVC的时候就已经察觉了:模板名称对应具体的Model元数据。所以,通过元数据,我们可以自定义要选择的模板。像是这样:

public class Person{
    [UIHint("Template A")]
    public string Name{ get; set;}

    [UIHint("Template B", "Mvc")]
    [UIHint("Template A")]
public string Sex{ get; set;} }

      它有两个构造函数:

public UIHintAttribute(string uiHint); 
public UIHintAttribute(string uiHint, string presentationLayer);

      对于最后一个属性Sex,最终采用的是Template B,因为它的presentationLayer的值为Mvc。

      本人完全是MVC新手,所讲的均是自己的理解,如果有说得不对的地方,还请各位大神指出。

     

 

    

     

posted @ 2013-04-03 22:20  文酱  阅读(3587)  评论(3编辑  收藏  举报