ASP.NET MVC3新增了许多新特性,IValidatableObject接口就是新增的诸多特性之一。ASPNET MBC3该书中是这样描述的:IValidatableObject 接口允许执行 Model 水平的验证,并且允许你提供整个模型状态的验证错误信息,或者基于 Model 的两个属性。当 Model 绑定的时候,MVC3 从 IValidatableObject 接收错误信息,在视图中使用内建的 HTML 助手时,将会自动标识或者高亮受影响的字段。
可能有人会问了,Mvc2中可以使用自定义验证来对模型进行验证,为什么mvc3中又新增了IValidatableObject这个借口呢?
不错,我们是可以通过继承ValidationAttribute类并且重写它的IsValid方法来自定义验证,但是这中做法有个局限性,那就是如果我需要验证模型的各个属性之间的逻辑关系,例如:我们有一个商品模型,我们要求这个模型的出库数量不能大于库存数量,那么自定义验证就显得不是那么给力了,这时候我们就需要ValidationAttribute接口了。
首先我们这个商品模型Product.cs需要实现ValidationAttribute接口,然后实现Validate这个方法。
03 |
public class Product:IValidatableObject |
04 |
{ |
05 |
/// <summary> |
06 |
/// 产品名称 |
07 |
/// </summary> |
08 |
[DisplayName( "产品名称" )] |
09 |
[AllowHtml] //允许输入html脚本 |
10 |
[Required] |
11 |
public String Name { get ; set ; } |
12 |
/// <summary> |
13 |
/// 库存数量 |
14 |
/// </summary> |
15 |
[DisplayName( "库存" )] |
16 |
[Required] |
17 |
public int Inventory { get ; set ; } |
18 |
|
19 |
/// <summary> |
20 |
/// 销售数量 |
21 |
/// </summary> |
22 |
[DisplayName( "售出" )] |
23 |
[Required] |
24 |
public int Shipping { get ; set ; } |
25 |
|
26 |
|
27 |
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) |
28 |
{ |
29 |
|
30 |
if (Shipping > Inventory) |
31 |
{ |
32 |
yield return new ValidationResult( "出库数量不能大于库存数量" , new string [] { "Shipping" }); |
33 |
} |
34 |
} |
35 |
|
36 |
|
37 |
|
38 |
} |
Index.cshtml
03 |
@model 数据验证.Product |
04 |
|
05 |
@{ |
06 |
Layout = null ; |
07 |
} |
08 |
|
09 |
<!DOCTYPE html> |
10 |
|
11 |
<html> |
12 |
<head> |
13 |
<title>Index</title> |
14 |
</head> |
15 |
<body> |
16 |
<script src= "http://www.cnblogs.com/Scripts/jquery-1.4.4.min.js" type= "text/javascript" ></script> |
17 |
<script src= "http://www.cnblogs.com/Scripts/jquery.validate.min.js" type= "text/javascript" ></script> |
18 |
<script src= "http://www.cnblogs.com/Scripts/jquery.validate.unobtrusive.min.js" type= "text/javascript" ></script> |
19 |
|
20 |
@ using (Html.BeginForm()) { |
21 |
@Html.ValidationSummary( true ) |
22 |
<fieldset> |
23 |
<legend>Product</legend> |
24 |
|
25 |
<div class = "editor-label" > |
26 |
@Html.LabelFor(model => model.Name) |
27 |
</div> |
28 |
<div class = "editor-field" > |
29 |
@Html.EditorFor(model => model.Name) |
30 |
@Html.ValidationMessageFor(model => model.Name) |
31 |
</div> |
32 |
|
33 |
<div class = "editor-label" > |
34 |
@Html.LabelFor(model => model.Inventory) |
35 |
</div> |
36 |
<div class = "editor-field" > |
37 |
@Html.EditorFor(model => model.Inventory) |
38 |
@Html.ValidationMessageFor(model => model.Inventory) |
39 |
</div> |
40 |
|
41 |
<div class = "editor-label" > |
42 |
@Html.LabelFor(model => model.Shipping) |
43 |
</div> |
44 |
<div class = "editor-field" > |
45 |
@Html.EditorFor(model => model.Shipping) |
46 |
@Html.ValidationMessageFor(model => model.Shipping) |
47 |
</div> |
48 |
|
49 |
<p> |
50 |
<input type= "submit" value= "Create" /> |
51 |
</p> |
52 |
</fieldset> |
53 |
} |
54 |
|
55 |
<div> |
56 |
@Html.ActionLink( "Back to List" , "Index" ) |
57 |
</div> |
58 |
|
59 |
</body> |
60 |
</html> |
当我们输入出库的数量大于库存的数量时,那么我们会得到错误提示,OK搞定了。
但是还有一个小问题,Validate方法中定义的错误提示,两个属性的名称是我们硬编码的,不利于我们的维护,试想假如有一天我们将Inventory和Shipping的Display属性分别改成别的名称,那么我们原来的错误提示”出库数量不能大于库存数量”就不太恰当了,为了解决这个问题,我们可以使用反射来获取这两个属性的DisplayName值,上代码:
EntityAttribute.cs
03 |
public class EntityAttribute |
04 |
{ |
05 |
private Type type; |
06 |
public EntityAttribute(Type type) |
07 |
{ |
08 |
this .type = type; |
09 |
} |
10 |
public string GetDisplayAttributeName( string propertyName) |
11 |
{ |
12 |
var propertyInfo = type.GetProperty(propertyName); |
13 |
object [] attrs = propertyInfo.GetCustomAttributes( typeof (DisplayNameAttribute), true ); |
14 |
return (attrs[0] as DisplayNameAttribute).DisplayName; |
15 |
} |
16 |
} |
这个EntityAttribute.cs类可以返回指定模型类的指定属性的DisplayName值。
然后我们在更新我们的Product.cs代码
03 |
public class Product:IValidatableObject |
04 |
{ |
05 |
/// <summary> |
06 |
/// 产品名称 |
07 |
/// </summary> |
08 |
[DisplayName( "产品名称" )] |
09 |
[AllowHtml] //允许输入html脚本 |
10 |
[Required] |
11 |
public String Name { get ; set ; } |
12 |
/// <summary> |
13 |
/// 库存数量 |
14 |
/// </summary> |
15 |
[DisplayName( "库存" )] |
16 |
[Required] |
17 |
public int Inventory { get ; set ; } |
18 |
|
19 |
/// <summary> |
20 |
/// 销售数量 |
21 |
/// </summary> |
22 |
[DisplayName( "售出" )] |
23 |
[Required] |
24 |
public int Shipping { get ; set ; } |
25 |
|
26 |
|
27 |
|
28 |
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) |
29 |
{ |
30 |
Type type = typeof (Product); |
31 |
EntityAttribute entityAttribute= new EntityAttribute(type); |
32 |
PropertyInfo[] PropertyInfos= type.GetProperties(); |
33 |
string shippingDisplayName = entityAttribute.GetDisplayAttributeName( "Shipping" ); |
34 |
string inventoryDisplayName = entityAttribute.GetDisplayAttributeName( "Inventory" ); |
35 |
if (Shipping > Inventory) |
36 |
{ |
37 |
yield return new ValidationResult( string .Format( "{0}数量不能大于{1}数量" , shippingDisplayName, inventoryDisplayName), new string [] { "Shipping" }); |
38 |
} |
39 |
} |
40 |
|
41 |
} |
这回终于大功告成了!