深入淺出 MVC 數據驗證 2.0 [附演示源碼]
上次的文章得到了很多讀者的支持,所以感謝一下大家,特別感謝指出不足之處的幾位朋友,我在原文中已經進行了修改。
P.S. 圖片中的 dozer.net.cn 其實打開后就是我博客園的地址,會自動跳轉,這個好記一點
今天在這里給大家介紹一下MVC的數據驗證框架。
在1.0版中,很多朋友提出了怎么使用客戶端驗證,今天找了一些資料,發現了客戶端驗證的方法。
1、MVC中的數據驗證框架有何優點?
在Asp.net時代,或者沒有使用MVC的驗證框架,一般是在BLL層中進行數據驗證,但是BLL層的返回值又只能返回一個東西,比如一個字符串,而實際情況中,數據驗證是很復雜的。
這時候,BLL層和網站會分離的不徹底,因為很多代碼不得不在網站中寫。
而在MVC的數據驗證框架中,甚至可以不用BLL層,而在比BLL層更底層的Model層書寫數據驗證的代碼。
并且最后能在網頁上顯示出來。
此圖這就是最后的效果

2、深入淺出?
此框架有個優點,非常靈活,我這里用正常的三層架構來寫。
因為靈活,我可以把數據驗證寫在任何一層。
i)寫在Controller里:這是最簡單的方法,但是也是最不推薦的方法, 因為不能體現分層思想
ii)寫在BLL中:如果對一個數據驗證的時候,需要牽扯到別的數據,就應該把驗證寫在這一層,比如一個Article Model的Category值是1,查詢這個分類是否存在
iii)寫在Model中:一些底層的標準應該寫在這一層,因為這些標準在任何情況下都不能違反,比如帳號名長度不能超過20個字符
下面,我會一步步講3中驗證方法介紹給大家
3、前端和后端的結合
完整地看過MVC教程的人應該都知道如何使用 ModelState,其實MVC驗證框架就是利用它,將驗證的結果顯示在頁面中。
下面看一個例子:
Controller
[HttpPost]
//如果表單中input的name屬性和Model的字段一樣,那可以直接以Model形式傳入一個Action
public ActionResult Exp1(Models.UserModel user)
{
//判斷
if (user.Name.Length > 20)
{
//如果錯誤,調用ModelState的AddModelError方法,第一個參數需要輸入出錯的字段名
ModelState.AddModelError("Name", "名字不得超過20個字符");
}
//判斷ModelState中是否有錯誤
if (ModelState.IsValid)
{
//如果沒錯誤,返回首頁
return RedirectToAction("Index");
}
else
{
//如果有錯誤,繼續輸入信息
return View(user);
}
}
這里在Controller中一個Action中進行了數據驗證,并且把結果放入了ModelState中,那怎么在前端頁面顯示呢?
如果不了解MVC的驗證框架,其實可以直接自動生成,看看標準做法
在代碼上右擊,點Add View
選擇創建強類型View,并且在內容中選擇Edit

這是自動生成的View

OK,下面我可以運行了。。。

由于前端的頁面View是自動生成的,所以有些讀者可能沒看懂,為什么我剛剛在后端的數據驗證信息會顯示到前端去了呢?
其實關鍵就是利用了這個:<%= Html.ValidationMessageFor(model => model.Name) %>
不理解強類型方法、或還在使用MVC1.0的讀者可以看這個:<%= Html.ValidationMessage("Name") %>
(除了ValidationMessage函數外,還有其它幾個函數,可以達到不同的效果,讀者可以自行研究下,這幾個函數都是以Validation開頭的)
小結:在Controller中驗證數據,放入ModelState(其核心是一個字典),然后在利用函數讀取
這樣,就達到了數據驗證時前端和后端相結合的效果。
4、如何將數據驗證代碼放入業務邏輯層?
上面那部分,我們看到了MVC數據驗證框架的核心,ModelState。
只要在ModelState中添加錯誤就可以在前端頁面中顯示了。
所以,這個部分的關鍵就是讓BLL操作ModelState
這里,有2中方法可以參考,其中,第二種方案我是參考了xVal來實現的
i)方案一:在調用BLL函數的時候直接傳入ModelState對象
優點:這個是最好理解的,我直接傳入ModelState對象,讓BLL操作它不就可以了?
缺點:BLL是業務邏輯層,它不應該知道自己被誰調用了,也就是說,BLL層中不應該出現任何MVC特有的東西(ModelState對象)
下面就讓我來實現它:
BLL
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcApplication1.BLL
{
public static class UserBLL
{
public static void Edit(Models.UserModel user, ModelStateDictionary ModelState)
{
if (user.Name.Length > 20)
{
//如果錯誤,調用ModelState的AddModelError方法,第一個參數需要輸入出錯的字段名
ModelState.AddModelError("Name", "名字不得超過20個字符");
}
if (ModelState.IsValid)
{
//在這里我可以寫一些代碼,因為完成了驗證,我就可以開始更新數據庫了
}
}
}
}
Controller
[HttpPost]
public ActionResult Exp2(Models.UserModel user)
{
//調用BLL中的函數
BLL.UserBLL.Edit(user, ModelState);
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
else
{
//這里,前端頁面不用改,所以我直接利用第一個例子中的前端頁面
return View("Exp1",user);
}
}
OK,直接運行,結果和上一個方法一樣
總結:在調用BLL的時候把ModelState傳入
ii)方案二:通過錯誤捕捉,將BLL和Controller關聯起來
如果使用方案一,會出現這樣一個問題:
如果我的項目不用MVC了,要移植怎么辦?
新的架構中也有類似于ModelState的東西。總不能把所有的BLL改一遍吧?
所以,我們需要方案二。
這里是通過編寫一個自定義Exception類,然后這個自定義Exception類有2個功能:
1、加入錯誤
2、把錯誤轉換到ModelState中(如果要轉移到別的架構,可以再寫一個新的轉移方法)
下面的代碼就是這個自定義Exception類
ModelExceptions
//必須繼承自Exception
public class ModelExceptions : Exception
{
//存放錯誤信息的List
List<string[]> errors = new List<string[]>();
//判斷是否有錯誤
public bool IsValid
{
get
{
return errors.Count == 0 ? true : false;
}
}
//增加錯誤信息
public void AddError(string name, string message)
{
this.errors.Add(new string[] { name, message });
}
//填充ModelState
public void FillModelState(ModelStateDictionary modelstate)
{
foreach (var e in this.errors)
{
modelstate.AddModelError(e[0], e[1]);
}
}
}
接下來是在Controller中的代碼
Controller
[HttpPost]
public ActionResult Exp3(Models.UserModel user)
{
//用try來捕捉錯誤
try
{
BLL.UserBLL.Edit(user);
}
catch (ModelExceptions e)
{
//如果發生了錯誤,就填充到ModelState中
e.FillModelState(ModelState);
}
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
else
{
//這里,前端頁面不用改,所以我直接利用第一個例子中的前端頁面
return View("Exp1", user);
}
}
然后是在BLL中的代碼
BLL
public static void Edit(Models.UserModel user)
{
var e = new ModelExceptions();
if (user.Name.Length > 20)
{
//如果錯誤,調用ModelState的AddModelError方法,第一個參數需要輸入出錯的字段名
e.AddError("Name", "名字不得超過20個字符");
}
if (e.IsValid)
{
//在這里我可以寫一些代碼,因為完成了驗證,我就可以開始更新數據庫了
}
else
{
//如果有錯誤,就拋出錯誤
throw e;
}
}
總結:簡單的做法,在后期會反而會帶來很多麻煩,所以推薦方案二。而且方案二也不是很麻煩,反而讓人感覺很清晰
5、如何將數據驗證代碼放入Model中?
前面,在BLL中驗證數據已經很好了,但是又出現了一個問題
一個Model,很多限制是固定的,比如長度不能超過20個字符
但是我在BLL中有很多過程,比如修改,刪除等
那我豈不是要在所有的過程中都多這個進行驗證?
其實你也可以通過寫一個函數來解決這個問題
但是,我(Model)的名字有沒有超過20個字符是我自己的事情,憑什么要你來鑒定?我自己說了算!
插播笑話一則:在我家,大事我說了算,小事我老婆說了算~ 那什么是大事?什么是小事?像美國打不打伊拉克,這就是大事;別的都是小事……
OK,言歸正傳…
如何把數據驗證交給Model呢?這里需要引用一個DLL

然后在Model中這樣做
Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace MvcApplication1.Models
{
public class UserModel
{
public string Name { get; set; }
//屬性前加上Attribute
[Required(ErrorMessage = "密碼不能為空")]
[StringLength(20, ErrorMessage = "密碼長度不能超過20個字符")]
public string Password { get; set; }
}
}
引用 System.ComponentModel.DataAnnotations 命名空間,并且在Model屬性前加上Attribute(C# 新特性:特征),這樣就可以了
請自行查看System.ComponentModel.DataAnnotations命名空間,看看可以有哪些驗證方法
當然也可以自定義驗證,查看默認驗證的定義,看看它繼承了哪個類,自己仿寫就可以了
我這里沒有取消掉BLL中的驗證,2種驗證可以混合使用
OK,那在Controller和BLL中需要做什么?我們需要做一定的修改
Controller
[HttpPost]
//MVC在傳入這個Model的時候已經進行了驗證,并且把錯誤放去了ModelState
public ActionResult Exp4(Models.UserModel user)
{
try
{
//別的不變,除了這里,我們需要傳入ModelState.IsValid
BLL.UserBLL.Edit(user,ModelState.IsValid);
}
catch (ModelExceptions e)
{
e.FillModelState(ModelState);
}
if (ModelState.IsValid)
{
return RedirectToAction("Index");
}
else
{
return View("Exp1", user);
}
}
BLL
public static void Edit(Models.UserModel user,bool IsValid)
{
var e = new ModelExceptions();
if (user.Name.Length > 20)
{
e.AddError("Name", "名字不得超過20個字符");
}
//別的不變,但在這里,我除了要判斷e中是否有錯誤外,還要判斷ModelState中是否有錯誤
if (e.IsValid && IsValid)
{
//在這里我可以寫一些代碼,因為完成了驗證,我就可以開始更新數據庫了
}
else
{
throw e;
}
}
我注釋了相對了上個例子改動的地方
并且混合使用了2中驗證方法

什么時候用Model驗證? 驗證Model固有的屬性
什么時候用BLL驗證? 當需要驗證一些復雜關系的時候
另外,為什么要把ModelState.IsValid傳入BLL?
因為Model驗證是在這個Model傳入這個方法的時候就已經完成的

如果不傳入,那BLL驗證中雖然沒錯誤,但不代表整個過程沒有錯誤。
對數據庫的操作要知道完整的驗證信息,如果不傳入,會導致程序BUG
總結:合理的根據情況來放置數據驗證代碼的位置才是王道
6、如何更改錯誤提示的樣式?
我這里用了MVC基本的那個例程,里面包含了CSS樣式表
而默認情況下,雖然會出現譬如“名稱過長”這樣的文字信息,但是卻沒有樣式(默認是正常字體,正常顏色)
那如何修改?
其實打開MVC默認的CSS樣式表就不難發現,這些錯誤信息都有固定的class,所以只要寫一個CSS的class即可

那怎么才能知道class名是什么呢? 最方便的方法,做好頁面后在瀏覽器中看一下即可
7、Entity Framework中,如何在Model中編寫數據驗證?
Entity Framework會自動生成Model,雖然是可以修改的,但是強烈建議不要直接修改Model原始代碼
其實微軟早就想到這一點了,它生成的Model都是 partial class(部分類)
也就是說,同一個類的代碼可以分幾部分,寫在不同的地方
具體寫法如下,寫在不同的地方,但需要在同一個命名空間下
User
[MetadataType(typeof(UserMetaData))]
public partial class User { }
public class UserMetaData
{
[Required(ErrorMessage = "名字為空")]
[StringLength(10, ErrorMessage = "名字長度不得超過10個字符")]
public string Name { get; set; }
[Required(ErrorMessage = "密碼為空")]
[StringLength(20, ErrorMessage = "密碼長度不得超過20個字符")]
public string Password { get; set; }
[Required(ErrorMessage = "帳號為空")]
[StringLength(10, ErrorMessage = "帳號長度不得超過10個字符")]
public string Passport { get; set; }
}
這樣寫好后,便可以在Entity Framework中使用Model驗證了
8、如何使用客戶端驗證
任何平臺都可以靠js來實現客戶端驗證,但是我這里探討的是MVC的數據驗證。
那MVC的客戶端數據驗證有什么不同呢?
不同之處就在于,你可以不用寫一行javascript代碼!
下面讓我們來實現它
先添加3個javascript文件,請按順序添加:

然后在View里添加一行代碼:(注意要添加在Form前)

注意點:這里,其實是這個函數把Model驗證轉換成了javascript代碼,對!它只能轉換Model驗證,BLL驗證無法轉換,因為BLL驗證涉及到復雜的代碼,不可能全部轉換成javascript吧?并且BLL驗證很多還需要和數據庫交互。
那如果想把BLL驗證也做成“客戶端”驗證怎么辦?(只有可能用ajax實現無刷新驗證,而不是真正的客戶端驗證)
目前看來先只能手寫了
如有收獲,我會繼續更新~
9、Ending

演示中的源碼(更新至2.0):下載
如果感覺有收獲,那就點一下支持吧~
如有疑問或者我文章中有不妥之處,請在下方留言,或者發送郵件到:dozer@dozer.net.cn

本作品采用知識共享署名-非商業性使用 3.0 Unported許可協議進行許可。


浙公網安備 33010602011771號