在ASP.NET MVC 使用Enterprise Library Validation Block 驗證
在ASP.NET MVC 2中,對於Model 引用 System.ComponentModel.DataAnnotations驗證機制,不管是TryValidateModel,或是DefaultModleBinder....
而Enterprise Library Validation Block 擴充了驗證的機制,使其方式更加靈活,但若是要將Enterprise Library Validation Block運用於ASP.NET MVC,必須做些調整,在此先介紹運用於ASP.NET MVC 的 DefaultModelBinder 機制。
若是不自建Model的Binding (實作 IModelBinder),預設物件的Binding是使用 DefaultModelBinder,有興趣的人可以去看SourceCode瞭解看看(可以參照上一篇文章在專案中加入 ASP MVC SourceCode 來 Debug ),這裡我就直接以 Enter prise Library Validation Block Lab 的範例作說明
1. 環境說明
Customer class
public class Customer {
public Customer() {}
[StringLengthValidator(1, 25)]
public string FirstName { get; set; }
[StringLengthValidator(1, 25)]
public string LastName { get; set; }
[RegexValidator(@"^\d\d\d-\d\d-\d\d\d\d$")]
public string SSN { get; set; }
[ObjectValidator]
public Address Address { get; set; }
}
public class Address {
[StringLengthValidator(1, 50)]
public string StreetAddress { get; set; }
[ValidatorComposition(CompositionType.And)]
[StringLengthValidator(1, 30)]
[ContainsCharactersValidator("sea", ContainsCharacters.All)]
public string City { get; set; }
[StringLengthValidator(2, 2)]
public string State { get; set; }
[RegexValidator(@"^\d{5}$")]
public string ZipCode { get; set; }
}CustomerController
public class CustomerController : Controller {
public ActionResult Index() {
ViewData.Model = new Customer();
return View();
}
[HttpPost]
public ActionResult Index(Customer customer) {
ViewData.Model = customer;
return View();
}
}
Index.aspx
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<% using (Html.BeginForm()) { %>
<%: Html.EditorForModel()%>
<input type="submit" value="Submit" />
<%} %>
</asp:Content>
我在EditorTemplates 建了Customer.ascx 與Address.axcs
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<TestMvc.Models.Customer>" %> First Name : <%: Html.TextBoxFor(m => m.FirstName) %> <%: Html.ValidationMessageFor( m => m.FirstName) %> <br /> Last Name : <%: Html.TextBoxFor(m => m.LastName) %> <%: Html.ValidationMessageFor( m => m.LastName) %> <br /> SSN : <%: Html.TextBoxFor(m => m.SSN) %> <%: Html.ValidationMessageFor( m => m.SSN) %> <br /> <fieldset> <legend>Address</legend> <%: Html.EditorFor(m => m.Address) %> </fieldset>
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<TestMvc.Models.Address>" %> Street Address : <%: Html.EditorFor(m => m.StreetAddress) %> <%: Html.ValidationMessageFor( m => m.StreetAddress) %> <br /> City : <%: Html.EditorFor(m => m.City) %> <%: Html.ValidationMessageFor( m => m.City) %> <br /> State : <%: Html.EditorFor(m => m.State) %> <%: Html.ValidationMessageFor( m => m.State) %> <br /> ZipCode : <%: Html.EditorFor(m => m.ZipCode) %> <%: Html.ValidationMessageFor( m => m.ZipCode) %>
畫面呈現
此刻若Submit,並不過發生預期的Validation,反而出現error
原因是因為上面的Customer class 中用到的ValidatorCompositionAttribute 有些不同之處,上面提到 ASP.NET MVC Validation 是引用System.ComponentModel.DataAnnotations機制,背後是些物件繼承ValidationAttribute,在驗證時觸發Validate 這個Method (相關的內容留待下次再說明),但ValidatorCompositionAttribute 雖繼承ValidationAttribute,但在Validate 中卻是 throw new NotSupportedException,在此要做一些修改。
2. 修改程式
這時要去看一下ASP.MET MVC 的 DefaultModelBinder,在BindComplexElementalModel這個Method 其中有一段Validation 的Code
internal void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) {
// need to replace the property filter + model object and create an inner binding context
ModelBindingContext newBindingContext = CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model);
// validation
if (OnModelUpdating(controllerContext, newBindingContext)) {
BindProperties(controllerContext, newBindingContext);
OnModelUpdated(controllerContext, newBindingContext);
}
}而 OnModelUpdated 這個Method 就是我們要下手的地方,此時Create 一個class 繼承DefaultModelBinder
MyModelBinder.cs(改用Enterprise Library Validation 的機制)
public class MyModelBinder : DefaultModelBinder {
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) {
//base.OnModelUpdated(controllerContext, bindingContext);
ValidatorFactory factory = EnterpriseLibraryContainer.Current.GetInstance<ValidatorFactory>();
var validator = factory.CreateValidator(bindingContext.ModelType);
Dictionary<string, bool> startedValid = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
foreach (var result in validator.Validate(bindingContext.Model)) {
string subPropertyName = CreateSubPropertyName(bindingContext.ModelName, result.Key);
if (!startedValid.ContainsKey(subPropertyName)) {
startedValid[subPropertyName] = bindingContext.ModelState.IsValidField(subPropertyName);
}
if (startedValid[subPropertyName]) {
bindingContext.ModelState.AddModelError(subPropertyName, result.Message);
}
}
}
}最後在global.asax.cs 中的Application_Start 設定一下,加入
ModelBinders.Binders.DefaultBinder = new MyModelBinder();
再去執行
3.結論
再此只是針對DefaultModelBinder 做變更,若是自建IModelBinder,或是Custom class 沒有應用ASP.NET MVC到 Model Binding,對於Validation 機制還是要額外處理,觀念大致相同。
不過老實說,站在系統架構的立場上,我個人並不喜歡將Validation 通通放在ASP.MET MVC 的Model上,因為Model 的Validation 機制雖然方便,但缺點卻是與ASP.NEt MVC 綁的太緊,high coupling,對於系統的分層設計上來說,不是一件好事,不過這是一個 tradeoff 選擇


