- ASP.Net MVC開發基礎學習筆記:一、走向MVC模式
- ASP.Net MVC開發基礎學習筆記:二、HtmlHelper與擴充套件方法
- ASP.Net MVC開發基礎學習筆記:三、Razor檢視引擎、控制器與路由機制學習
一、校驗 — 表單不是你想提想提就能提
1.1 DataAnnotations(資料註解)
位於 System.ComponentModel.DataAnnotations 名稱空間中的特性指定對資料模型中的各個欄位的驗證。這些特性用於定義常見的驗證模式,例如範圍檢查和必填欄位。而 DataAnnotations 特性使 MVC 能夠提供客戶端和伺服器驗證檢查,使你無需進行額外的編碼來控制資料的有效。
通過為模型類增加資料描述的 DataAnnotations ,我們可以容易地為應用程式增加驗證的功能。DataAnnotations 允許我們描述希望應用在模型屬性上的驗證規則,ASP.NET MVC 將會使用這些 DataAnnotations ,然後將適當的驗證資訊返回給使用者。
在DataAnnotations為我們所提供的眾多內建驗證特性中,用的最多的其中的四個是:
(0)[DisplayName]:顯示名 – 定義表單欄位的提示名稱
(1)[Required] :必須 – 表示這個屬性是必須提供內容的欄位
(2)[StringLength]:字串長度 – 定義字串型別的屬性的最大長度
(3)[Range]:範圍 – 為數字型別的屬性提供最大值和最小值
(4)[RegularExpression]:正規表示式 – 指定動態資料中的資料欄位值必須與指定的正規表示式匹配
1.2 使用DataAnnotations為Model進行校驗
假設我們的Model中有一個UserInfo的實體,其定義如下:
1 2 3 4 5 6 |
public class UserInfo { public int Id { get; set; } public string UserName { get; set; } public int Age { get; set; } } |
UserInfo的屬性很簡單,只有三個:Id,UserName和Age三個欄位;現在我們可以為其增加驗證特性,看看其為我們提供的強大的校驗功能。
(1)非空驗證
新增特性:
1 2 3 4 5 6 7 |
[Display(Name="使用者名稱")] [Required(ErrorMessage = "*姓名必填")] public string UserName { get; set; } [Display(Name = "年齡")] [Required(ErrorMessage = "*年齡必填")] public int Age { get; set; } |
驗證效果:
(2)字串長度驗證
新增特性:
1 2 3 4 |
[Display(Name="使用者名稱")] [Required(ErrorMessage = "*姓名必填")] [StringLength(5, ErrorMessage = "*長度必須小於5")] public string UserName { get; set; } |
驗證效果:
(3)範圍驗證
新增特性:
1 2 3 4 |
[Display(Name = "年齡")] [Required(ErrorMessage = "*年齡必填")] [Range(18, 120)] public int Age { get; set; } |
驗證效果:
(4)正規表示式驗證
新增特性:驗證使用者輸入的是否是數字,正規表示式匹配
1 2 3 4 5 |
[Display(Name = "年齡")] [Required(ErrorMessage = "*年齡必填")] [Range(18, 120)] [RegularExpression(@"^\d+$", ErrorMessage = "*請輸入合法數字")] public int Age { get; set; } |
驗證效果:
(5)瀏覽所生成的HTML程式碼
從上圖可以看出,我們在瀏覽器端的校驗都是通過為html標籤設定自定義屬性來實現的,我們在Model中為其新增的各種校驗特性,都會在客戶端生成一個特定的屬性,例如:data-val-length-max=“5”與data-val-length=”*長度必須小於5″對應[StringLength(5, ErrorMessage = “*長度必須小於5”)]。然後,通過jquery validate在客戶端每次提交之前進行校驗,如果校驗匹配中有不符合規則的,則將message顯示在一個特定的span標籤(class=”field-validation-valid”)內,並阻止此次表單提交操作。
1.3 使用DataAnnotations的注意事項
(1)首先,要確保需要進行校驗的頁面中引入了指定的幾個js檔案:
1 2 |
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> |
當然,jquery庫的js檔案也是必須的,而且在上面這兩個js之前引入;
(2)在 Web.config 的appSettings中,已經預設支援了客戶端驗證(MVC3.0及更高版本中預設支援,MVC2.0則需要修改一下):
1 2 3 |
<!-- 是否啟用全域性客戶端校驗 --> <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" /> |
PS:Unobtrusive Javascript有三層含義:
一是在HTML程式碼中不會隨意的插入Javsscript程式碼,只在標籤中加一些額外的屬性值,然後被引用的指令碼檔案識別和處理;
二是通過指令碼檔案所增加的功能是一種漸進式的增強,當客戶端不支援或禁用了Javsscript時網頁所提供的功能仍然能夠實現,只是使用者體驗會降低;
三是能夠相容不同的瀏覽器。
(3)在Action中如果要對客戶端是否通過了校驗進行驗證,可以通過以下程式碼實現:
1 2 3 4 5 6 7 8 9 10 |
[HttpPost] public ActionResult Add(UserInfo userInfo) { if (ModelState.IsValid) { // To do fun } return RedirectToAction("Index"); } |
如果通過校驗,則ModelState的IsValid屬性(bool型別)會變為true,反之則為false。
二、ASP.Net MVC下的兩種AJAX方式
2.1 使用JQuery AJAX方式
首先,在ASP.Net MVC中使用此種方式跟普通的WebForm的開發方式是一致的,需要注意的是:Url地址不同->請求的是Controller下的Action,例如在WebForm中請求的url通常是/Ajax/UserHandler.ashx,而在MVC中請求的url通常為:/User/GetAll。
例如,我們在一個View中新增一個按鈕,用於使用AJAX獲取一個伺服器端的時間:
1 2 |
<h1>JQuery Ajax方式</h1> <input id="btnJQuery" type="button" value="獲取伺服器時間" /> |
在Home控制器中增加一個用於返回時間的Action:
1 2 3 |
public ActionResult GetServerDate() {return Content(DateTime.Now.ToString()); } |
在View中增加一段JQuery程式碼,為btnJQuery按鈕繫結一個Click事件:
1 2 3 4 5 6 7 8 9 |
$(function () { $("#btnJQuery").click(function () { $.post("/Home/GetServerDate", {}, function (data) { if (data != null) { $("#spTime").html(data); } }); }); }); |
這裡通過JQuery AJAX傳送一個非同步的POST請求,獲取伺服器時間結果,並將其顯示在span標籤內:
至此,一個使用JQuery Ajax的MVC頁面就完成了。但是,這僅是一個最簡單的AJAX示例,在實際開發中往往比較複雜一點。
需要注意的是:
(1)如果你在JQuery AJAX中使用的是get方式的提交,那麼在在使用Json返回JsonResult時注意要將第二個引數設定允許Get提交方式:return Json(“”,JsonRequestBehavior.AllowGet),否則你用get方式是無權執行要請求的Action方法的。
(2)在Ajax開發中要注意Ajax方法體內的引數設定正確,特別是引數名要和Action中的引數名保持一致;
(3)如果在Action中為其設定了[HttpPost]或[HttpGet],那麼提交方式要跟Action打的標籤一致;
2.2 使用Microsoft AJAX方式
在ASP.Net MVC中除了可以使用JQuery AJAX外,Microsoft為我們提供了另一套實用且更簡單的AJAX方案,我們姑且稱其為:Microsoft AJAX方式。
(1)首先:
需要將微軟提供的js指令碼引入到頁面中:其實就是jquery.unobtrusive-ajax.js
1 2 |
<script src="~/Scripts/jquery-1.7.1.min.js"></script> <script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script> |
確保在Web.config中啟用了Unobtrusive JavaScript
1 |
<add key="UnobtrusiveJavaScriptEnabled" value="true" /> |
(2)其次,使用Ajax.BeginForm方法構造一個form表單:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<h1>Microsoft Ajax方式</h1> @using (Ajax.BeginForm("GetServerDate", "Home", new AjaxOptions() { HttpMethod = "POST", Confirm = "您確定要提交?", InsertionMode = InsertionMode.Replace, UpdateTargetId = "spResult", OnSuccess = "afterSuccess", LoadingElementId="loading" })) { <table> <tr> <td>使用者名稱:</td> <td> <input id="txtUserName" name="UserName" /></td> </tr> <tr> <td>密 碼:</td> <td> <input id="txtPassword" name="Password" /></td> </tr> <tr> <td align="center" colspan="2"> <input id="btnAjax" type="submit" value="提 交" /> </td> </tr> <tr> <td align="center" colspan="2"> <div id="loading" style="display:none"> <img style="vertical-align:middle" src="~/Content/ico_loading2.gif" />正在獲取中,請稍候... </div> <span id="spResult"></span> </td> </tr> </table> } |
這裡需要注意的是:
①Ajax.BeginForm沒有提供閉合的方法,需要使用Using配合關閉;
②AjaxOptions引數的設定:
HttpMethod代表此次AJAX請求到底是POST方式還是GET方式?這裡是POST方式;
Confirm代表點選提交按鈕後提出的確認對話方塊,並給出使用者給定的提示語,這裡是:您確定要提交?
InsertionMode代表請求獲得後的資料是要替換還是追加,一般選擇替換,即Replace;
UpdateTargetId代表需要替換的div標籤的Id,這裡是一個span標籤,代表需要顯示的資訊都顯示在這個span內;
OnSuccess代表請求成功後所需要執行的回撥方法,是一個js方法,可以自定義,這裡是一個function afterSuccess()的方法;
1 2 3 |
function afterSuccess(data) { //alert("您已成功獲取資料:" + data); } |
LoadingElementId=”loading”是一個很有意思的屬性,代表在ajax請求期間為了提供良好的使用者體驗,可以給出一個正在載入中的提示,而這個LoadingElementId則代表一個提示的div區域的Id。這裡主要是指id為loading的這個div,其中有一張gif圖片及一句話:正在獲取中,請稍等…的提示。
1 2 3 |
<div id="loading" style="display:none"> <img style="vertical-align:middle" src="~/Content/ico_loading2.gif" />正在獲取中,請稍候... </div> |
為了顯示載入提示的效果,我們人為地修改一下Action方法,使用Thread.Sleep(3000)來延遲一下請求返回時間
1 2 3 4 5 |
public ActionResult GetServerDate() { System.Threading.Thread.Sleep(3000); return Content(DateTime.Now.ToString()); } |
好了,現在我們可以看一下效果如何:
到此,我們的Microsoft AJAX就算完成了一個最簡單的Demo了。那麼,我們不禁想知道Microsoft AJAX是怎麼做到的?跟校驗一樣,我們瀏覽一下生成的form表單就知道了:
原來我們在AjaxOptions中所設定的引數也被解析成了form的自定義屬性,它們的對應關係如下:
三、為AOP而生 — ASP.Net MVC預設的過濾器
3.1 過濾器初步
AOP:Aspect Oriented Programming(AOP)是較為熱門的一個話題。AOP,國內大致譯作“面向切面程式設計”。針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。主要的功能是:日誌記錄,效能統計,安全控制,事務處理,異常處理等等。
3.2 微軟提供的幾種預設過濾器
微軟預設為我們提供了四種型別的過濾器(Filter),如下圖所示:
這裡,我們主要來看看ActionFilter(Action過濾器)和ExceptionFilter(異常過濾器)的使用:
(1)Action Filter
ActionFilterAttribute預設實現了IActionFilter和IResultFilter。而ActionFilterAttribute是一個Abstract的型別,所以不能直接使用,因為它不能例項化,所以我們想使用它必須繼承一下它然後才能使用。
①因此,我們首先在Models中新建一個類,取名為:MyActionFilterAttribute(以Attribute結尾比較符合編碼規範),並使其繼承自ActionFilterAttribute,然後重寫基類所提供的虛方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
public class MyActionFilterAttribute : ActionFilterAttribute { public string Name { get; set; } /// <summary> /// Action 執行之前先執行此方法 /// </summary> /// <param name="filterContext">過濾器上下文</param> public override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); HttpContext.Current.Response.Write("<br />OnActionExecuting :" + Name); } /// <summary> /// Action執行之後 /// </summary> /// <param name="filterContext">過濾器上下文</param> public override void OnActionExecuted(ActionExecutedContext filterContext) { base.OnActionExecuted(filterContext); HttpContext.Current.Response.Write("<br />OnActionExecuted :" + Name); } /// <summary> /// ActionResult執行之前先執行此方法 /// </summary> /// <param name="filterContext">過濾器上下文</param> public override void OnResultExecuting(ResultExecutingContext filterContext) { base.OnResultExecuting(filterContext); HttpContext.Current.Response.Write("<br />OnResultExecuting :" + Name); } /// <summary> /// ActionResult執行之後先執行此方法 /// </summary> /// <param name="filterContext">過濾器上下文</param> public override void OnResultExecuted(ResultExecutedContext filterContext) { base.OnResultExecuted(filterContext); HttpContext.Current.Response.Write("<br />OnResultExecuted :" + Name); } } |
這裡我們重寫了四個虛方法,他們各自代表了在Action執行之前和之後需要執行的業務邏輯,以及在Result執行之前和之後需要執行的業務邏輯。這裡的Result主要是指我們在Action中進行return 語句返回結果時(例如:return Content(“Hello Filter!”);),之前和之後要執行的邏輯處理。
比如:我們想要在每個Action執行之前進行使用者是否登入的校驗,可以在OnActionExecuting中判斷使用者Session是否存在,如果存在則繼續執行Action的具體業務程式碼,如果不存在則重定向頁面到登陸頁,後邊的Action業務程式碼不再執行。
②現在有了自定義的過濾器,我們怎麼將其應用到Action中呢?這裡有三種方式:
一是給某個控制器的某個Action指定此Filter:
1 2 3 4 5 6 |
[MyActionFilter(Name = "Filter Action")] public ActionResult Filter() { Response.Write("<p>Action正在努力執行中...</p>"); return Content("<p>OK:檢視成功被渲染</p>"); } |
二是給某個控制器的所有Action指定此Filter:
1 2 3 4 |
[MyActionFilter(Name="Home Filter")] public class HomeController : Controller { } |
但是,要注意的是:如果既給Controller指定了Filter,又給該Controller中的某個Action指定了Filter,那麼具體的這個Action以離其定義最近的Filter為準,也就是一個優先順序的順序問題:Action的Filter優先順序高於Controller的Filter。
三是給此專案中的所有控制器即全域性指定此Filter:在App_Start中更改FilterConfig類,此種方式優先順序最低。
1 2 3 4 5 6 |
public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); // 註冊自定義Action過濾器:優先順序最低,但是可以作用到所有的控制器和Action filters.Add(new MyActionFilterAttribute() { Name = "Global Controller" }); } |
③現在我們來看看具體的效果:
可以看到,我們的/Home/Filter這個Action中只有兩句程式碼,一句Response.Write,另一句是return Content();在Response.Write之前執行了OnActionExecuting的過濾器方法,之後則執行了OnActionExecuted的過濾器方法;我們剛剛說了,在Action中的return語句代表了Result,那麼在Result之前執行了OnResultExecuting過濾器方法,之後則執行了OnResultExecuted過濾器方法。這裡僅僅是為了展示,在實際開發中是需要寫一些具體的業務邏輯處理的,例如:判斷使用者的登入狀態,記錄使用者的操作日誌等等。
(2)Exception Filter
①同樣,在Models中新建一個類,取名為:MyExceptionFilterAttribute,並使其繼承自HandleErrorAttribute。
1 2 3 4 5 6 7 8 9 |
public class MyExceptionFilterAttribute : HandleErrorAttribute { public override void OnException(ExceptionContext filterContext) { base.OnException(filterContext); HttpContext.Current.Response.Redirect("/Home/Index"); } } |
這裡,重寫基類的OnException方法,這裡僅僅為了演示效果,沒有對異常進行處理。在實際開發中,需要獲取異常物件,並將其記錄至日誌中。例如,下面一段程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public override void OnException(ExceptionContext filterContext) { base.OnException(filterContext); //獲取系統異常訊息記錄 string strException = filterContext.Exception.Message; if (!string.IsNullOrEmpty(strException)) { //使用Log4Net記錄異常資訊 Exception exception = filterContext.Exception; if (exception != null) { LogHelper.WriteErrorLog(strException, exception); } else { LogHelper.WriteErrorLog(strException); } } filterContext.HttpContext.Response.Redirect("~/GlobalErrorPage.html"); } |
②有了異常過濾器,我們怎麼來應用到專案中呢?答案也在App_Start中,還是在FilterConfig類中,新添一句程式碼進行註冊:
1 2 3 4 5 6 7 8 9 10 11 |
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); // 註冊自定義Action過濾器:優先順序最低,但是可以作用到所有的控制器和Action filters.Add(new MyActionFilterAttribute() { Name = "Global Controller" }); // 註冊自定義Exception過濾器 filters.Add(new MyExceptionFilterAttribute()); } } |
③為了測試,我們新增一個Action,使其能夠出現一個異常:DividedByZero
1 2 3 4 5 6 7 |
public ActionResult Exception() { int a = 10; int b = 0; int c = a / b; return Content("Exception is happened."); } |
④當我們測試這個Action時,會發現系統執行了自定義的異常過濾器,將我們的這個請求改為重定向到Index這個Action了。
參考資料
(1)蔣金楠,《ASP.NET MVC下的四種驗證程式設計方式》,http://www.cnblogs.com/artech/p/asp-net-mvc-validation-programming.html
(2)蔣金楠,《ASP.NET MVC下的四種驗證程式設計方式[續篇]》,http://www.cnblogs.com/artech/p/asp-net-mvc-4-validation.html
(3)馬倫,《ASP.NET MVC 2014特供教程》,http://bbs.itcast.cn/thread-26722-1-1.html
(4)w809026418,《MVC中使用 DataAnnotations 進行模型驗證》,http://www.cnblogs.com/haogj/archive/2011/11/16/2251920.html
(5)劉俊峰,《ASP.NET MVC中Unobtrusive Ajax的妙用》,http://www.cnblogs.com/rufi/archive/2012/03/31/unobtrusive-ajax.html