前提:数据建模是通过ADO.Net Entity 生成。原型如下:
1 [EdmEntityTypeAttribute(NamespaceName="GoodsModel", Name="Customer")]
2 [Serializable()]
3 [DataContractAttribute(IsReference=true)]
4 public partial class Customer : EntityObject
5 {
6 #region Factory Method
7
8 /// <summary>
9 /// Create a new Customer object.
10 /// </summary>
11 /// <param name="code">Initial value of the Code property.</param>
12 public static Customer CreateCustomer(global::System.Int32 code)
13 {
14 Customer customer = new Customer();
15 customer.Code = code;
16 return customer;
17 }
18
19 #endregion
20 #region Primitive Properties
21
22 /// <summary>
23 /// No Metadata Documentation available.
24 /// </summary>
25 [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
26 [DataMemberAttribute()]
27 public global::System.Int32 Code
28 {
29 get
30 {
31 return _Code;
32 }
33 set
34 {
35 if (_Code != value)
36 {
37 OnCodeChanging(value);
38 ReportPropertyChanging("Code");
39 _Code = StructuralObject.SetValidValue(value);
40 ReportPropertyChanged("Code");
41 OnCodeChanged();
42 }
43 }
44 }
45 private global::System.Int32 _Code;
46 partial void OnCodeChanging(global::System.Int32 value);
47 partial void OnCodeChanged();
48
49 /// <summary>
50 /// No Metadata Documentation available.
51 /// </summary>
52 [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
53 [DataMemberAttribute()]
54 [ValidateLength(200)]
55 public global::System.String Company
56 {
57 get
58 {
59 return _Company;
60 }
61 set
62 {
63 OnCompanyChanging(value);
64 ReportPropertyChanging("Company");
65 _Company = StructuralObject.SetValidValue(value, true);
66 ReportPropertyChanged("Company");
67 OnCompanyChanged();
68 }
69 }
2 [Serializable()]
3 [DataContractAttribute(IsReference=true)]
4 public partial class Customer : EntityObject
5 {
6 #region Factory Method
7
8 /// <summary>
9 /// Create a new Customer object.
10 /// </summary>
11 /// <param name="code">Initial value of the Code property.</param>
12 public static Customer CreateCustomer(global::System.Int32 code)
13 {
14 Customer customer = new Customer();
15 customer.Code = code;
16 return customer;
17 }
18
19 #endregion
20 #region Primitive Properties
21
22 /// <summary>
23 /// No Metadata Documentation available.
24 /// </summary>
25 [EdmScalarPropertyAttribute(EntityKeyProperty=true, IsNullable=false)]
26 [DataMemberAttribute()]
27 public global::System.Int32 Code
28 {
29 get
30 {
31 return _Code;
32 }
33 set
34 {
35 if (_Code != value)
36 {
37 OnCodeChanging(value);
38 ReportPropertyChanging("Code");
39 _Code = StructuralObject.SetValidValue(value);
40 ReportPropertyChanged("Code");
41 OnCodeChanged();
42 }
43 }
44 }
45 private global::System.Int32 _Code;
46 partial void OnCodeChanging(global::System.Int32 value);
47 partial void OnCodeChanged();
48
49 /// <summary>
50 /// No Metadata Documentation available.
51 /// </summary>
52 [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
53 [DataMemberAttribute()]
54 [ValidateLength(200)]
55 public global::System.String Company
56 {
57 get
58 {
59 return _Company;
60 }
61 set
62 {
63 OnCompanyChanging(value);
64 ReportPropertyChanging("Company");
65 _Company = StructuralObject.SetValidValue(value, true);
66 ReportPropertyChanged("Company");
67 OnCompanyChanged();
68 }
69 }
- 这种情况下最直接,也就是最容易想到的是,在控制器中直接调用ModelState.AddModelError().
1 [HttpPost]
2 public ActionResult Create(Customer customer)
3 {
4 if (customer.Company.Length > ComanyMaxLength)
5 ModelState.AddModelError("Company", "Company must be at most ComanyMaxLength characters long");
6
7 if (ModelState.IsValid)
8 {
9 return View(customer);
10 }
11 else
12 {
13 new CustomerModels().CreateCustomer(customer);
14 return RedirectToAction("Details", "Customer", new { ID = customer.Code });
15 }
16 }
2 public ActionResult Create(Customer customer)
3 {
4 if (customer.Company.Length > ComanyMaxLength)
5 ModelState.AddModelError("Company", "Company must be at most ComanyMaxLength characters long");
6
7 if (ModelState.IsValid)
8 {
9 return View(customer);
10 }
11 else
12 {
13 new CustomerModels().CreateCustomer(customer);
14 return RedirectToAction("Details", "Customer", new { ID = customer.Code });
15 }
16 }
这样写简单,而且我们还有更简单的办法就是
- 我们可以直接给MVC 中model filed 加上ValidateAttribute,下面是模板生成的ChangePasswordModel 中的代码。
1 [Required]
2 [ValidatePasswordLength]
3 [DataType(DataType.Password)]
4 [DisplayName("New password")]
5 public string NewPassword { get; set; }
2 [ValidatePasswordLength]
3 [DataType(DataType.Password)]
4 [DisplayName("New password")]
5 public string NewPassword { get; set; }
给所有需要的字段加上CustomerAttribute. 但问题来了,数据的里面有那么多的表,每个字段的长度也不一样,一一对应起来肯定麻烦,也容易出错。而且ADO.Net Entity 生成的Model 要加CustomerAttribute 也不是那么容易,同样工作量大,耦合性强。那有没有更好的方法呢,
- 那就是利用ModelBinder 扩展,通过MetadatWorkspace 读取csdl的元数据进行验证。
1 public class ValidationModelBinder : DefaultModelBinder
2 {
3 protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
4 {
5 base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
6
7 if(value==null) return;
8 var edmWorkspace = new MetadataWorkspace(new[] { "res://*/Entity.Goods.csdl" }, new Assembly[] { Assembly.GetAssembly(typeof(Customer)) });
9 foreach (var edmType in edmWorkspace.GetItems<EdmType>(DataSpace.CSpace).Where(p => p.NamespaceName != "Edm" && p.Name == propertyDescriptor.ComponentType.Name))
10 {
11 ReadOnlyMetadataCollection<Facet> facetList = (edmType as EntityType).Properties.First(p => p.Name == propertyDescriptor.Name).TypeUsage.Facets;
12 facetList.Any(p =>
13 {
14 if (p.Name == "MaxLength")
15 {
16 if ((int)p.Value < value.ToString().Length)
17 bindingContext.ModelState.AddModelError(propertyDescriptor.Name, "Company must be at most " + p.Value + " characters long");
18 return true;
19 }
20 return false;
21 });
22 }
23 }
24
25 }
2 {
3 protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
4 {
5 base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
6
7 if(value==null) return;
8 var edmWorkspace = new MetadataWorkspace(new[] { "res://*/Entity.Goods.csdl" }, new Assembly[] { Assembly.GetAssembly(typeof(Customer)) });
9 foreach (var edmType in edmWorkspace.GetItems<EdmType>(DataSpace.CSpace).Where(p => p.NamespaceName != "Edm" && p.Name == propertyDescriptor.ComponentType.Name))
10 {
11 ReadOnlyMetadataCollection<Facet> facetList = (edmType as EntityType).Properties.First(p => p.Name == propertyDescriptor.Name).TypeUsage.Facets;
12 facetList.Any(p =>
13 {
14 if (p.Name == "MaxLength")
15 {
16 if ((int)p.Value < value.ToString().Length)
17 bindingContext.ModelState.AddModelError(propertyDescriptor.Name, "Company must be at most " + p.Value + " characters long");
18 return true;
19 }
20 return false;
21 });
22 }
23 }
24
25 }
这样来耦合问题解决,大大减少了工作量。上面的代码需要优化,每个对象的属性绑定时都会调用,剩下的问题自己解决吧。将这个类注册下就可以了。
1 protected void Application_Start()
2 {
3 AreaRegistration.RegisterAllAreas();
4
5 ModelBinders.Binders.Add(typeof(DataFacotry.Entity.Customer), new GoodsMvc.Component.ValidationModelBinder());
6
7 RegisterRoutes(RouteTable.Routes);
8 }
2 {
3 AreaRegistration.RegisterAllAreas();
4
5 ModelBinders.Binders.Add(typeof(DataFacotry.Entity.Customer), new GoodsMvc.Component.ValidationModelBinder());
6
7 RegisterRoutes(RouteTable.Routes);
8 }
同时也可以通过重载OnModelUpdated, 具体区别我也没有测试。
1 protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
这样看其来万事大吉了。 但我还是倾向于给ADO.Net Entity 生成的Model 添加CustomerAttribute. 到底能不能工作量少的情况下给所有的Filed 添加CustomerAttribute 呢?
- 今天的重头戏来了,就是修改ADO.Net Entity 的生成GeneratorTemplate (T4 template)的实现。语法可以参考:http://msdn.microsoft.com/en-us/library/bb126478.aspx 在数据模型(.edmx file)的试图模式下,右键->Add code generation Item...
可以搜索DataMemberAttribute在模板文件.tt中,定位到以下代码,添加高亮代码。
1 /// <summary>
2 /// <#=SummaryComment(primitiveProperty)#>
3 /// </summary><#=LongDescriptionCommentElement(primitiveProperty, 1)#>
4 [EdmScalarPropertyAttribute(EntityKeyProperty=<#=code.CreateLiteral(ef.IsKey(primitiveProperty))#>, IsNullable=<#=code.CreateLiteral(ef.IsNullable(primitiveProperty))#>)]
5 [DataMemberAttribute()]
6 <#+ if(primitiveProperty.TypeUsage.Facets.Any(p=>p.Name=="MaxLength")){#>
7 [ValidateLength(<#= primitiveProperty.TypeUsage.Facets["MaxLength"].Value #>)]
8 <#+
9 }
10 #>
2 /// <#=SummaryComment(primitiveProperty)#>
3 /// </summary><#=LongDescriptionCommentElement(primitiveProperty, 1)#>
4 [EdmScalarPropertyAttribute(EntityKeyProperty=<#=code.CreateLiteral(ef.IsKey(primitiveProperty))#>, IsNullable=<#=code.CreateLiteral(ef.IsNullable(primitiveProperty))#>)]
5 [DataMemberAttribute()]
6 <#+ if(primitiveProperty.TypeUsage.Facets.Any(p=>p.Name=="MaxLength")){#>
7 [ValidateLength(<#= primitiveProperty.TypeUsage.Facets["MaxLength"].Value #>)]
8 <#+
9 }
10 #>
ValidataLength 继承于ValidationAttribute。