詳解ASP.NET MVC數據驗證的一個特殊方法
這里我們將介紹ASP.NET MVC數據驗證實現的一個特殊方法,包括數據的驗證,驗證后數據的提交等等。51CTO編輯推薦《ASP.NET MVC框架視頻教程》。
關于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類的另一部分,代碼如下:
- public partial class news
- {
- partial void OnValidate(System.Data.Linq.ChangeAction action)
- {
- if (!IsValid)
- {
- throw new ApplicationException("驗證內容項出錯!");
- }
- }
- public bool IsValid
- {
- get { return (GetRuleViolations().Count() == 0); }
- }
- public IEnumerable<RuleViolation> GetRuleViolations()
- {
- if (String.IsNullOrEmpty(this.title .Trim () ))
- yield return new RuleViolation("題目步能為空!", "題目");
- if (String.IsNullOrEmpty(this.contents .Trim ()))
- yield return new RuleViolation("內容不能為空!", "內容");
- yield break;
- }
- }
- /// <summary>
- /// 規則信息類
- /// summary>
- public class RuleViolation
- {
- public string ErrorMessage { get; private set; }
- public string PropertyName { get; private set; }
- public RuleViolation(string errorMessage)
- {
- ErrorMessage = errorMessage;
- }
- public RuleViolation(string errorMessage, string propertyName)
- {
- ErrorMessage = errorMessage;
- PropertyName = propertyName;
- }
- }
在這里給出這么多代碼,其實是提前有設計的,因為從業務角度考慮,還不應該寫這部分代碼。RuleViolation類很簡單,就是一個包括了兩個屬性的類(這個類的結構設計是根據后面的ModelState.AddModelError主法來設計的)。
在news分部類中,有一個IsValid的屬性,這個屬性是bool類型的,返回值取決于GetRuleViolations這個方法,這個方法返回值是一個IEnumerable
現在驗證用戶數據,生成錯誤列表的工作都做完了,但關鍵是怎么能讓用戶提交數據時,調用OnValidate。這個問題,先放一下,請記住,上面的代碼,只要在用戶提交數據時,調用OnValidate,這樣就能得到錯誤集合。
現在,讓我們來處理Cotroller和View層,在Cotroller層,首先來添加index這個Action,代碼如下:
- public ActionResult Index()
- {
- var NewsList = DCDC.news.Select(newss=>newss);
- return View(NewsList );
- }
這個Action返回所有news表中的記錄。
對應的View如下:
- <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage
>" %>- <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>
- <th>
- title
- th>
- <th>
- datetimes
- th>
- <th>
- contents
- th>
- <th>
- IsValid
- th>
- tr>
- <% foreach (var item in Model) { %>
- <tr>
- <td>
- <%= Html.ActionLink("Edit", "Edit", new { id=item.ID }) %> |
- <%= Html.ActionLink("Details", "Details", new { id=item.ID })%>
- td>
- <td>
- <%= Html.Encode(item.ID) %>
- td>
- <td>
- <%= Html.Encode(item.title) %>
- td>
- <td>
- <%= Html.Encode(String.Format("{0:g}", item.datetimes)) %>
- td>
- <td>
- <%= Html.Encode(item.contents) %>
- td>
- <td>
- <%= Html.Encode(item.IsValid) %>
- td>
- tr>
- <% } %>
- table>
- <p>
- <%= Html.ActionLink("Create New", "Create") %>
- p>
- asp:Content>
代碼中,需要我們注意是的 <%= Html.ActionLink("Edit", "Edit", new { id=item.ID }) %>
因為要導航到Edit的View,把以接下來我們創建Edit的Action和View(因為在編輯數據時,要用到驗證,Edit才是我們的重點)。
- public ActionResult Edit(int id)
- {
- var list = DCDC.news.Single(newss=>newss.ID ==id);
- return View(list);
- }
<%= Html.ActionLink("Edit", "Edit", new { id=item.ID }) %>中的id會被當成參數送到EditController的Edit(int id)的Action,成為Edit方法的實參。
Edit.aspx頁面如下圖:
對應Edit的Action生成view,代碼如下:
- <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage
" %>- <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
- 編輯
- asp:Content>
- <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
- <h2 style ="text-align :left ;">編輯h2>
- <%= Html.ValidationSummary("Edit was unsuccessful. Please correct the errors and try again.") %>
- <% using (Html.BeginForm())
- { %>
- <fieldset>
- <legend>詳細內容legend>
- <p>
- <label for="title">標題:label>
- <%= Html.TextBox("title", Model.title) %>
- <%= Html.ValidationMessage("題目", "*")%>
- p>
- <p>
- <label for="datetimes">時間:label>
- <%= Html.TextBox("datetimes", String.Format("{0:g}", Model.datetimes)) %>
- <%= Html.ValidationMessage("時間", "*") %>
- p>
- <p>
- <label for="contents">內容:label>
- <%= Html.TextBox("contents", Model.contents) %>
- <%= Html.ValidationMessage("內容", "*")%>
- p>
- <p>
- <input type="submit" value="更新" />
- p>
- fieldset>
- <% } %>
- <div>
- <%=Html.ActionLink("Back to List", "Index") %>
- div>
- asp:Content>
如果要單擊“更新”返回數據新數據,還需要我們寫如下一個Action:
- [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捕獲到,就執行如下代碼:
- foreach (var v in Sig_news.GetRuleViolations())
- {
- this.ModelState.AddModelError(v.PropertyName,v.ErrorMessage);
- }
- return View(Sig_news);
這行代碼的意思是把錯誤的信息,以鍵值的方式放入ModelState中,ModelState是一個ModelStateDictionary類型,這個類型實現了IDictionary
再次利用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就有了用處了,這個值要與<%= Html.ValidationMessage("題目", "*")%>中的***個參數對應,這樣<%= Html.ValidationMessage("題目", "*")%>才能起到作用,顯示出第二個參數“*”。
這樣一來,就達到了ASP.NET MVC的數據驗證。由于ASP.NET MVC驗證拐的彎比較多,所以下來用個圖來說明一下。
【編輯推薦】