Asp.net MVC 示例項目"Suteki.Shop"分析之---數據驗證
在Suteki.Shop,實現了自己的數據校驗機制,可以說其設計思路還是很有借鑒價值的。而使用
這種機制也很容易在Model中對相應的實體對象(屬性)添加校驗操作方法。下面就來介紹一下其實
現方式。
首先,看一下這樣類圖:

在Suteki.Shop定義一個“IValidatingBinder”接口,其派生自IModelBinder:
{
void UpdateFrom(object target, NameValueCollection values);
void UpdateFrom(object target, NameValueCollection values, string objectPrefix);
void UpdateFrom(object target, NameValueCollection values, ModelStateDictionary modelStateDictionary);
void UpdateFrom(object target, NameValueCollection values, ModelStateDictionary modelStateDictionary, string objectPrefix);
}
其接口中定義了一個重載方法UpdateFrom,其要實現的功能與MVC中UpdateFrom一樣,就是自動讀取
我們在form中定義的有些元素及其中所包含的內容。
實現IValidatingBinder接口的類叫做:ValidatingBinder,下面是其核心代碼說明。
首先是BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
該方法是在IModelBinder接口中定義的,是其核心功能,用于將客戶端數據轉成我們希望Model類型。
/// IModelBinder.BindModel
/// </summary>
/// <param name="controllerContext"></param>
/// <param name="bindingContext"></param>
/// <returns></returns>
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException("bindingContext");
}
if (IsBasicType(bindingContext.ModelType))
{
return new DefaultModelBinder().BindModel(controllerContext, bindingContext);
}
var instance = Activator.CreateInstance(bindingContext.ModelType);
var request = controllerContext.HttpContext.Request;
var form = request.RequestType == "POST" ? request.Form : request.QueryString;
UpdateFrom(instance, form);
return instance;
}
上面代碼第二個if 用于判斷bindingContext的Model類型是否是系統類型,比如decimal,string等。
如果是則使用MVC自帶的DefaultModelBinder來進行處理。否則就使用該類自己的UpdateFrom方法,從而
實現對當前form中的數據與Model中相應類型的信息綁定,并返相應的 Model 實例(instance)。下面
是其核心代碼:
{
foreach (var property in bindingContext.Target.GetType().GetProperties())
{
try
{
foreach (var binder in propertyBinders)
{
binder.Bind(property, bindingContext);
}
}
catch (Exception exception)
{
if (exception.InnerException is FormatException ||
exception.InnerException is IndexOutOfRangeException)
{
string key = BuildKeyForModelState(property, bindingContext.ObjectPrefix);
bindingContext.AddModelError(key, bindingContext.AttemptedValue, "Invalid value for {0}".With(property.Name));
bindingContext.ModelStateDictionary.SetModelValue(key, new ValueProviderResult(bindingContext.AttemptedValue, bindingContext.AttemptedValue, CultureInfo.CurrentCulture));
}
else if (exception is ValidationException)
{
string key = BuildKeyForModelState(property, bindingContext.ObjectPrefix);
bindingContext.AddModelError(key, bindingContext.AttemptedValue, exception.Message);
bindingContext.ModelStateDictionary.SetModelValue(key, new ValueProviderResult(bindingContext.AttemptedValue, bindingContext.AttemptedValue, CultureInfo.CurrentCulture));
}
else if (exception.InnerException is ValidationException)
{
string key = BuildKeyForModelState(property, bindingContext.ObjectPrefix);
bindingContext.AddModelError(key, bindingContext.AttemptedValue, exception.InnerException.Message);
bindingContext.ModelStateDictionary.SetModelValue(key, new ValueProviderResult(bindingContext.AttemptedValue, bindingContext.AttemptedValue, CultureInfo.CurrentCulture));
}
else
{
throw;
}
}
}
if (!bindingContext.ModelStateDictionary.IsValid)
{
throw new ValidationException("Bind Failed. See ModelStateDictionary for errors");
}
}
上面代碼中的TRY部分就是其數據綁定的代碼,而其Catch部分實現了在數據綁定過程中出現的
錯誤異常(主要是數據驗證等,會在后面提到)收集到ModelState(ModelStateDictionary)中
以便于后續處理。而這里Suteki.Shop還定義了自己的驗證異常類“ValidationException”(位于:
Suteki.Common\Validation\ValidationException.cs,因為代碼很簡單,就不多做解釋了。
有了ValidatingBinder之后,下面就來看一下Suteki.Shop是如何使用它的。這里以一個業務
流程---“編輯用戶”來進行說明。
下面就是UserController(Suteki.Shop\Controllers\UserController.cs) 中的Edit操作:
public ActionResult Edit([DataBind] User user, string password)
{
if(! string.IsNullOrEmpty(password))
{
user.Password = userService.HashPassword(password);
}
.. }
在該Action中,我們看到其定義并使用了DataBind這個ModelBinder進行綁定處理,所以我們要
先看一下DataBinder(注:它是Suteki.Shop中關于數據綁定的“ModelBinder的基類)中倒底做了
些什么,下面是其實現代碼:
{
public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object entity;
if(declaringAttribute == null || declaringAttribute.Fetch)
{
entity = FetchEntity(bindingContext, controllerContext);
}
else
{
entity = Activator.CreateInstance(bindingContext.ModelType);
}
try
{
validatingBinder.UpdateFrom(entity, controllerContext.HttpContext.Request.Form, bindingContext.ModelState, bindingContext.ModelName);
}
catch(ValidationException ex)
{}
return entity;
}


}
其BindModel方法中“獲取當前要編輯的用戶數據操作”就是通過下面這一行完成的:
而try中的代碼validatingBinder.UpdateFrom()就是對上面所說的“ValidatingBinder”中的
“UpdateFrom”調用。通過UpdateFrom之后就會將綁定時出現的錯誤異常進行收集。
有了這種綁定,可以說設置上完成了,而如何將驗證規則綁定到相應的Model對象上呢?
為了實現這個功能,Suteki.Shop提供了一個叫做ValidationProperty的泛型類,它提供了對于
數字,是否為空, IsDecimal,最大值,最小值,IsEmail等驗證功能。并以擴展方法的行式提供出
來,相應代碼如下:
使用它就可以很方便的對Model中的相關屬性添加驗證規則了。以User為例,其驗證規則添加
內容如下(Suteki.Shop\Models\User.cs):
{
Validator validator = new Validator
{
() => Email.Label("Email").IsRequired().IsEmail(),
() => Password.Label("Password").IsRequired(),
};
validator.Validate();
}
在規則添加完成后,就把對獲取到的信息進行驗證了,下面是驗證的實現方法:
{
public void Validate()
{
var errors = new List<ValidationException>();
foreach (Action validation in this)
{
try
{
validation();
}
catch (ValidationException validationException)
{
errors.Add(validationException);
}
}
if (errors.Count > 0)
{
//backwards compatibility
string error = string.Join("", errors.Select(x => x.Message + "<br />").ToArray());
throw new ValidationException(error, errors);
}
}
}
代碼比較簡單,大家看一下就可以了。
到這里,主要的代碼就介紹完了,下面再后到UserController中看看Action是如何調用驗證方法
并發驗證錯誤信息復制到ModelState中的,接著看一下編輯用戶信息這個Action:
public ActionResult Edit([DataBind] User user, string password)
{
if(! string.IsNullOrEmpty(password))
{
user.Password = userService.HashPassword(password);
}
try
{
user.Validate();
}
catch (ValidationException validationException)
{
validationException.CopyToModelState(ModelState, "user");
return View("Edit", EditViewData.WithUser(user));
}
return View("Edit", EditViewData.WithUser(user).WithMessage("Changes have been saved"));
}
大家看到了吧,Try中的user.Validate()就是啟動驗證的功能,而在Catch中使用CopyToModelState
方法將錯誤信息Copy到當前Controller中的ModelState中,如下:
{
foreach(var error in errors)
{
string key = string.IsNullOrEmpty(prefix) ? error.propertyKey : prefix + "." + error.propertyKey;
dictionary.AddModelError(key, error.Message);
}
}
這樣在前臺View中,通過Html.ValidationSummary()方法來顯示驗證結果,現在我們看一下最終的
運行效果:
以“輸入錯誤的Email地址”為例:

好了,今天的內容就先到這里了。
原文鏈接:http://www.rzrgm.cn/daizhj/archive/2009/05/18/1452735.html
作者: daizhj,代震軍,LaoD
Tags: mvc,Suteki.Shop
網址: http://daizhj.cnblogs.com/

浙公網安備 33010602011771號