在ASP.NET MVC 使用Enterprise Library Validation Block 驗證

  • 8509
  • 0

在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();

再去執行

這樣子,應有的Validation就發揮作用了

3.結論

再此只是針對DefaultModelBinder 做變更,若是自建IModelBinder,或是Custom class 沒有應用ASP.NET MVC到 Model Binding,對於Validation 機制還是要額外處理,觀念大致相同。

不過老實說,站在系統架構的立場上,我個人並不喜歡將Validation 通通放在ASP.MET MVC 的Model上,因為Model 的Validation 機制雖然方便,但缺點卻是與ASP.NEt MVC 綁的太緊,high coupling,對於系統的分層設計上來說,不是一件好事,不過這是一個 tradeoff  選擇