轉:ASP.Net MVC:校驗、AJAX與過濾器

原文地址:http://blog.jobbole.com/85005/

 

1、校驗 — 表單不是你想提想提就能提

1.1 DataAnnotations(數據註解)

位於 System.ComponentModel.DataAnnotations 命名空間中的特性指定對數據模型中的各個字段的驗證。這些特性用於定義常見的驗證模式,例如範圍檢查和必填字段。而 DataAnnotations 特性使 MVC 可以提供客戶端和服務器驗證檢查,使你無需進行額外的編碼來控制數據的有效javascript

經過爲模型類增長數據描述的 DataAnnotations ,咱們能夠容易地爲應用程序增長驗證的功能。DataAnnotations 容許咱們描述但願應用在模型屬性上的驗證規則,ASP.NET MVC 將會使用這些 DataAnnotations ,而後將適當的驗證信息返回給用戶。html

在DataAnnotations爲咱們所提供的衆多內置驗證特性中,用的最多的其中的四個是:java

(0)[DisplayName]:顯示名 – 定義表單字段的提示名稱jquery

(1)[Required] :必須 – 表示這個屬性是必須提供內容的字段ajax

(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。

2、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的自定義屬性,它們的對應關係以下:

3、爲AOP而生 — ASP.Net MVC默認的過濾器

3.1 過濾器初步

  大一點的項目總會有相關的AOP面向切面的組件,而MVC(特指:Asp.Net MVC,如下皆同)項目中Action在執行前或者執行後咱們想作一些特殊的操做(好比身份驗證,日誌,異常,行爲截取等),而不想讓MVC開發人員去關心和寫這部分重複的代碼。那麼,咱們能夠經過AOP截取實現,而在MVC項目中咱們就能夠直接使用它提供的Filter的特性幫咱們解決,不用本身實現複雜的AOP了。
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("OnActionExecuting :" + Name);
     }
 
     /// <summary>
     /// Action執行以後
     /// </summary>
     /// <param name="filterContext">過濾器上下文</param>
     public override void OnActionExecuted(ActionExecutedContext filterContext)
     {
         base.OnActionExecuted(filterContext);
         HttpContext.Current.Response.Write("OnActionExecuted :" + Name);
     }
 
     /// <summary>
     /// ActionResult執行以前先執行此方法
     /// </summary>
     /// <param name="filterContext">過濾器上下文</param>
     public override void OnResultExecuting(ResultExecutingContext filterContext)
     {
         base.OnResultExecuting(filterContext);
         HttpContext.Current.Response.Write("OnResultExecuting :" + Name);
 
     }
 
     /// <summary>
     /// ActionResult執行以後先執行此方法
     /// </summary>
     /// <param name="filterContext">過濾器上下文</param>
     public override void OnResultExecuted(ResultExecutedContext filterContext)
     {
         base.OnResultExecuted(filterContext);
         HttpContext.Current.Response.Write("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

相關文章
相關標籤/搜索