關于ASP.NET MVC的驗證,用起來很特别,因為MS的封裝,使人了解起來很費解。也可能很多人都在Scott Guthrie等人寫的一本《ASP.NET MVC 1.0》書中,見過NerdDinner項目中對Dinner對象修改和添加的時的資料驗證。但有許多封裝的地方,不知道是怎樣的工作原理,今天研究了,拿出來給大家分享一下。
資料庫還是上一篇blog中的庫與表,同樣的方法來建立news表的實體類,在自動生成的news這個實體類中,我們發現有一個特殊的分部方法:
partial void OnValidate(System.Data.Linq.ChangeAction action);
這個方法沒有實作,我們根據C#的文法知道,如果分部類中的分部方法,沒有實作的話,調用和定議的地方都不會起什麼作用。現在,我們要去完善這個方法,讓它“用”起來。
首先,人産在Models中建立news類的另一部分,代碼如下:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
Code
1
public partial class news
2
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
{
3
partial void OnValidate(System.Data.Linq.ChangeAction action)
4
5
if (!IsValid)
6
7
throw new ApplicationException("驗證内容項出錯!");
8
}
9
}
10
public bool IsValid
11
12
get
{ return (GetRuleViolations().Count() == 0); }
13
14
public IEnumerable<RuleViolation> GetRuleViolations()
15
16
if (String.IsNullOrEmpty(this.title .Trim () ))
17
yield return new RuleViolation("題目步能為空!", "題目");
18
if (String.IsNullOrEmpty(this.contents .Trim ()))
19
yield return new RuleViolation("内容不能為空!", "内容");
20
yield break;
21
22
}
23
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
/**////
24
/// 規則資訊類
25
///
26
public class RuleViolation
27
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
28
public string ErrorMessage
{ get; private set; }
29
public string PropertyName
30
31
public RuleViolation(string errorMessage)
32
33
ErrorMessage = errorMessage;
34
35
36
public RuleViolation(string errorMessage, string propertyName)
37
38
39
PropertyName = propertyName;
40
41
42
在這裡給出這麼多代碼,其實是提前有設計的,因為從業務角度考慮,還不應該寫這部分代碼。RuleViolation類很簡單,就是一個包括了兩個屬性的類(這個類的結構設計是根據後面的ModelState.AddModelError主法來設計的)。
在news分部類中,有一個IsValid的屬性,這個屬性是bool類型的,傳回值取決于GetRuleViolations這個方法,這個方法傳回值是一個IEnumerable類型的,IEnumerable是通過news的幾個屬性是否為空來生成跌代的。如果title或contents為Null或””,就傳回跌代。其實真正的使用者資料的驗證就是在這裡實作,使用者的資料的對與錯,就是一個邏輯,隻要使用者資料不符合規則,就可以 “yield return new RuleViolation("錯誤辨別","錯誤提示資訊!")”;這裡的錯誤碼提示資訊是顯示到用戶端的,是以要處理好友好的提示。
現在驗證使用者資料,生成錯誤清單的工作都做完了,但關鍵是怎麼能讓使用者送出資料時,調用OnValidate。這個問題,先放一下,請記住,上面的代碼,隻要在使用者送出資料時,調用OnValidate,這樣就能得到錯誤集合。
現在,讓我們來處理Cotroller和View層,在Cotroller層,首先來添加index這個Action,代碼如下:
1
public ActionResult Index()
2
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
{
3
var NewsList = DCDC.news.Select(newss=>newss);
4
return View(NewsList );
5
}
6
這個Action傳回所有news表中的記錄。
對應的View如下:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Index
asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Indexh2>
<table>
<tr>
<th>th>
<th>
ID
th>
title
datetimes
contents
IsValid
tr>
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
<td>
|
td>
43
44
45
46
47
48
49
50
51
52
53
54
55
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
56
57
table>
58
59
<p>
60
61
p>
62
63
代碼中,需要我們注意是的
因為要導航到Edit的View,把以接下來我們建立Edit的Action和View(因為在編輯資料時,要用到驗證,Edit才是我們的重點)。
public ActionResult Edit(int id)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
var list = DCDC.news.Single(newss=>newss.ID ==id);
return View(list);
中的id會被當成參數送到EditController的Edit(int id)的Action,成為Edit方法的實參。
Edit.aspx頁面如下圖:
對應Edit的Action生成view,代碼如下:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
編輯
<h2 style ="text-align :left ;">編輯h2>
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
<fieldset>
<legend>詳細内容legend>
<p>
<label for="title">标題:label>
p>
<label for="datetimes">時間:label>
<label for="contents">内容:label>
<input type="submit" value="更新" />
fieldset>
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
<div>
div>
如果要單擊“更新”傳回資料新資料,還需要我們寫如下一個Action:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id,FormCollection formValuews)
{
news Sig_news = DCDC.news.Single(newss => newss.ID == id);
try
{
Sig_news.title = formValuews.GetValue("title").AttemptedValue;
Sig_news.datetimes = DateTime.Parse(formValuews.GetValue("datetimes").AttemptedValue);
Sig_news.contents = formValuews.GetValue("contents").AttemptedValue;
DCDC.SubmitChanges();
return RedirectToAction("Index");
catch
{
foreach (var v in Sig_news.GetRuleViolations())
{
ModelState.AddModelError(v.PropertyName,v.ErrorMessage);
}
return View(Sig_news);
這個Edit的Action是使用者送出返來更新資料庫的,我們可以從formValuews得到使用者在頁面上更新的資料,來更新Sig_news對象,然後調用DCDC.SubmitChanges();去更新資料庫,如果沒有民常,會導航到index.aspx頁面。如果發生異常,就會運作到catch裡。如果還記得,在本文的前半部分,我們說到OnValidate,是資料在送出時應該驗證,但在這裡,我們并沒有顯示的調用OnValidate這個方法,但實際運作中,我們發現,這個方法被執行了,如果我們建立跟蹤,把斷點設在DCDC.SubmitChanges();如果我們資料有民常,會發現當DCDC.SubmitChanges();執行完後就會跳到partial void OnValidate(System.Data.Linq.ChangeAction action)這個方法,這是怎麼做到的呢?我們猜測,一定是在資料送出時,調用OnValidate這個方法。為了找到它們的關系,隻好用Reflector.exe來“探測”一下了(Reflector.exe的用法就不說了)。
SubmitChanges方法是DataContext的一個方法,這個類位于System.Data.Linq命空間下,用Reflector.exe打開SubmitChanges,看到this.SubmitChanges(ConflictMode.FailOnFirstConflict);定位這個方法,可以看到new ChangeProcessor(this.services, this).SubmitChanges(failureMode);定位查找會發現ValidateAll(orderedList);在這個方法中,多處看到 SendOnValidate(obj2.Type, obj2, ChangeAction.Insert);這個方法,再定位,有這樣一行代碼 type.OnValidateMethod.Invoke(item.Current, new object[] { changeAction });這裡,正是通過反射調用了OnValidate這個方法。這樣我們就找到了SubmitChanges執行時調用OnValidate的方法了(其不用調用OnValidate也可以驗證使用者資料,隻需要寫個方法,在SubmitChanges 送出以前執行就可以達到同樣效果)。同時,當發生異常時,OnValidate會抛出一個Application的異常,這裡會被public ActionResult Edit(int id,FormCollection formValuews)方法中的Catch捕獲到,就執行如下代碼:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
foreach (var v in Sig_news.GetRuleViolations())
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
this.ModelState.AddModelError(v.PropertyName,v.ErrorMessage);
這行代碼的意思是把錯誤的資訊,以鍵值的方式放入ModelState中,ModelState是一個ModelStateDictionary類型,這個類型實作了IDictionary, ICollection>, IEnumerable>, IEnumerable這些接口(這裡要注意,ModelState是目前對象的一個屬性,并且它的AddModelError方法的第一個參數key有其獨特的作用)。處理完異常後,還是傳回目前頁面。這時你會發現,在頁面的 發生了變化,把我們錯誤的地方去提示出來了,這裡就是,為什麼我們把錯誤資訊放到ModelState中,而錯誤則顯示在了Html.ValidationSummary中了呢?并且發生錯誤的資料後會加上了一個紅色的“*”,這是怎麼樣做到的呢?
再次利用Reflector.exe,檢視Html.ValidationSummary方法和Html.ValidationMessage方法,會發現它們顯示的資料是從ModelState 中擷取的,如果ModelState 這個集合中沒有資料,Html.ValidationSummary和Html.ValidationMessage就傳回空,如果發生異常,this.ModelState中有子項,就會通過Html.ValidationSummary和Html.ValidationMessage在頁面頁上顯示出來。因為Html.ValidationMessage在頁面上有多個,是以在this.ModelState.AddModelError(v.PropertyName,v.ErrorMessage);方法中的v.PropertyName就有了用處了,這個值要與中的第一個參數對應,這樣才能起到作用,顯示出第二個參數“*”。
這樣一來,就達到了ASP.NET MVC的資料驗證。由于ASPNET MVC 驗證捌的彎比較多,是以下來用個圖來說明一下。