詳解ASP.NET MVC數(shù)據(jù)驗(yàn)證的一個特殊方法
這里我們將介紹ASP.NET MVC數(shù)據(jù)驗(yàn)證實(shí)現(xiàn)的一個特殊方法,包括數(shù)據(jù)的驗(yàn)證,驗(yàn)證后數(shù)據(jù)的提交等等。51CTO編輯推薦《ASP.NET MVC框架視頻教程》。
關(guān)于ASP.NET MVC數(shù)據(jù)驗(yàn)證,用起來很特別,因?yàn)镸S的封裝,使人理解起來很費(fèi)解。也可能很多人都在Scott Guthrie等人寫的一本《ASP.NET MVC 1.0》書中,見過NerdDinner項(xiàng)目中對Dinner對象修改和添加的時的數(shù)據(jù)驗(yàn)證。但有許多封裝的地方,不知道是怎樣的工作原理,今天研究了,拿出來給大家分享一下。
數(shù)據(jù)庫還是上一篇blog中的庫與表,同樣的方法來創(chuàng)建news表的實(shí)體類,在自動生成的news這個實(shí)體類中,我們發(fā)現(xiàn)有一個特殊的分部方法:
- partial void OnValidate(System.Data.Linq.ChangeAction action);
這個方法沒有實(shí)現(xiàn),我們根據(jù)C#的語法知道,如果分部類中的分部方法,沒有實(shí)現(xiàn)的話,調(diào)用和定議的地方都不會起什么作用?,F(xiàn)在,我們要去完善這個方法,讓它“用”起來。
首先,人產(chǎn)在Models中創(chuàng)建news類的另一部分,代碼如下:
- public partial class news
- {
- partial void OnValidate(System.Data.Linq.ChangeAction action)
- {
- if (!IsValid)
- {
- throw new ApplicationException("驗(yàn)證內(nèi)容項(xiàng)出錯!");
- }
- }
- 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("內(nèi)容不能為空!", "內(nèi)容");
- yield break;
- }
- }
- /// <summary>
- /// 規(guī)則信息類
- /// 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;
- }
- }
在這里給出這么多代碼,其實(shí)是提前有設(shè)計(jì)的,因?yàn)閺臉I(yè)務(wù)角度考慮,還不應(yīng)該寫這部分代碼。RuleViolation類很簡單,就是一個包括了兩個屬性的類(這個類的結(jié)構(gòu)設(shè)計(jì)是根據(jù)后面的ModelState.AddModelError主法來設(shè)計(jì)的)。
在news分部類中,有一個IsValid的屬性,這個屬性是bool類型的,返回值取決于GetRuleViolations這個方法,這個方法返回值是一個IEnumerable
現(xiàn)在驗(yàn)證用戶數(shù)據(jù),生成錯誤列表的工作都做完了,但關(guān)鍵是怎么能讓用戶提交數(shù)據(jù)時,調(diào)用OnValidate。這個問題,先放一下,請記住,上面的代碼,只要在用戶提交數(shù)據(jù)時,調(diào)用OnValidate,這樣就能得到錯誤集合。
現(xiàn)在,讓我們來處理Cotroller和View層,在Cotroller層,首先來添加index這個Action,代碼如下:
- public ActionResult Index()
- {
- var NewsList = DCDC.news.Select(newss=>newss);
- return View(NewsList );
- }
這個Action返回所有news表中的記錄。
對應(yīng)的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 }) %>
因?yàn)橐獙?dǎo)航到Edit的View,把以接下來我們創(chuàng)建Edit的Action和View(因?yàn)樵诰庉嫈?shù)據(jù)時,要用到驗(yàn)證,Edit才是我們的重點(diǎn))。
- 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會被當(dāng)成參數(shù)送到EditController的Edit(int id)的Action,成為Edit方法的實(shí)參。
Edit.aspx頁面如下圖:
對應(yīng)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>詳細(xì)內(nèi)容legend>
- <p>
- <label for="title">標(biāo)題: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">內(nèi)容:label>
- <%= Html.TextBox("contents", Model.contents) %>
- <%= Html.ValidationMessage("內(nèi)容", "*")%>
- p>
- <p>
- <input type="submit" value="更新" />
- p>
- fieldset>
- <% } %>
- <div>
- <%=Html.ActionLink("Back to List", "Index") %>
- div>
- asp:Content>
如果要單擊“更新”返回?cái)?shù)據(jù)新數(shù)據(jù),還需要我們寫如下一個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是用戶提交返來更新數(shù)據(jù)庫的,我們可以從formValuews得到用戶在頁面上更新的數(shù)據(jù),來更新Sig_news對象,然后調(diào)用DCDC.SubmitChanges();去更新數(shù)據(jù)庫,如果沒有民常,會導(dǎo)航到index.aspx頁面。如果發(fā)生異常,就會運(yùn)行到catch里。如果還記得,在本文的前半部分,我們說到OnValidate,是數(shù)據(jù)在提交時應(yīng)該驗(yàn)證,但在這里,我們并沒有顯示的調(diào)用OnValidate這個方法,但實(shí)際運(yùn)行中,我們發(fā)現(xiàn),這個方法被執(zhí)行了,如果我們建立跟蹤,把斷點(diǎn)設(shè)在DCDC.SubmitChanges();如果我們數(shù)據(jù)有民常,會發(fā)現(xiàn)當(dāng)DCDC.SubmitChanges();執(zhí)行完后就會跳到partial void OnValidate(System.Data.Linq.ChangeAction action)這個方法,這是怎么做到的呢?我們猜測,一定是在數(shù)據(jù)提交時,調(diào)用OnValidate這個方法。為了找到它們的關(guān)系,只好用Reflector.exe來“探測”一下了(Reflector.exe的用法就不說了)。
SubmitChanges方法是DataContext的一個方法,這個類位于System.Data.Linq命空間下,用Reflector.exe打開SubmitChanges,看到this.SubmitChanges(ConflictMode.FailOnFirstConflict);定位這個方法,可以看到new ChangeProcessor(this.services, this).SubmitChanges(failureMode);定位查找會發(fā)現(xiàn)ValidateAll(orderedList);在這個方法中,多處看到 SendOnValidate(obj2.Type, obj2, ChangeAction.Insert);這個方法,再定位,有這樣一行代碼 type.OnValidateMethod.Invoke(item.Current, new object[] { changeAction });這里,正是通過反射調(diào)用了OnValidate這個方法。這樣我們就找到了SubmitChanges執(zhí)行時調(diào)用OnValidate的方法了(其不用調(diào)用OnValidate也可以驗(yàn)證用戶數(shù)據(jù),只需要寫個方法,在SubmitChanges 提交以前執(zhí)行就可以達(dá)到同樣效果)。同時,當(dāng)發(fā)生異常時,OnValidate會拋出一個Application的異常,這里會被public ActionResult Edit(int id,FormCollection formValuews)方法中的Catch捕獲到,就執(zhí)行如下代碼:
- foreach (var v in Sig_news.GetRuleViolations())
- {
- this.ModelState.AddModelError(v.PropertyName,v.ErrorMessage);
- }
- return View(Sig_news);
這行代碼的意思是把錯誤的信息,以鍵值的方式放入ModelState中,ModelState是一個ModelStateDictionary類型,這個類型實(shí)現(xiàn)了IDictionary
再次利用Reflector.exe,查看Html.ValidationSummary方法和Html.ValidationMessage方法,會發(fā)現(xiàn)它們顯示的數(shù)據(jù)是從ModelState 中獲取的,如果ModelState 這個集合中沒有數(shù)據(jù),Html.ValidationSummary和Html.ValidationMessage就返回空,如果發(fā)生異常,this.ModelState中有子項(xiàng),就會通過Html.ValidationSummary和Html.ValidationMessage在頁面頁上顯示出來。因?yàn)镠tml.ValidationMessage在頁面上有多個,所以在this.ModelState.AddModelError(v.PropertyName,v.ErrorMessage);方法中的v.PropertyName就有了用處了,這個值要與<%= Html.ValidationMessage("題目", "*")%>中的***個參數(shù)對應(yīng),這樣<%= Html.ValidationMessage("題目", "*")%>才能起到作用,顯示出第二個參數(shù)“*”。
這樣一來,就達(dá)到了ASP.NET MVC的數(shù)據(jù)驗(yàn)證。由于ASP.NET MVC驗(yàn)證拐的彎比較多,所以下來用個圖來說明一下。
【編輯推薦】