下面我們繼續之前的ASP.NET MVC學習之過濾器篇(1)進行學習。
3.動作過濾器
顧名思義,這個過濾器就是在動作方法調用前與調用后響應的。我們可以在調用前更改實際調用的動作,也可以在動作調用完成之后更改最終返回的結果,當然很多人一定不太明白這個到底可以干什么,
下面我們舉一個比較實際的例子:
相信理解過網站的安全的一定知道跨站請求(CSRF具體可以自行百度,這里我就不去解釋了),當然也有解決方案,那就是給頁面中增加一個識別碼,當頁面進行POST請求時,首先判斷識別碼是否正確,
如果正確則繼續進行操作。並且在執行完成之后重新分配一個新的識別碼(當然也可以只用一個識別碼直到會話結束),這樣就可以加大進行跨站請求的難度。而動作過濾器的所處的生命周期剛好符合,
下面我們就開始編寫這個過濾器類。
首先我們在Filter文件夾中新建一個ViewMacFilterAttribute類,並且這個類需要繼承FilterAttribute,同時還要實現IActionFilter接口:
1 namespace MvcStudy.Filter 2 { 3 public class ViewMacFilterAttribute : FilterAttribute , IActionFilter 4 { 5 6 public void OnActionExecuted(ActionExecutedContext filterContext) 7 { 8 string viewMac = Guid.NewGuid().ToString(); 9 filterContext.HttpContext.Session["vmac"] = viewMac; 10 filterContext.Controller.ViewBag.ViewMac = viewMac; 11 } 12 13 public void OnActionExecuting(ActionExecutingContext filterContext) 14 { 15 object viewMac = filterContext.HttpContext.Session["vmac"]; 16 string strMac = filterContext.HttpContext.Request.Form["viewMac"]; 17 filterContext.Result = new HttpNotFoundResult(); 18 if (viewMac != null && strMac != null) 19 { 20 if (viewMac.Equals(strMac)) 21 { 22 filterContext.Result = null; 23 } 24 } 25 } 26 } 27 }
這里我們實現了IActionFilter 接口中的OnActionExecuted方法和OnActionExecuting方法,它們分別對應着動作執行結束和動作執行前,通過OnActionExecuting中的代碼,我們可以清楚的看到我們首先從Session中獲取識別碼,然后又從頁面中的表單獲取客戶端的識別碼,
這里我們可以看到我們首先默認返回的結果是404,如果比配成功則將Result設置為NULL,如果Result為NULL是會正常調用對應的活動的。接着就是OnActionExecuted方法中的功能,僅僅只是重新分配一個識別碼,並且保存進Session中。
注:這種方式會導致一個頁面進行了POST請求之后,識別碼變換之后。其他頁面的提交功能就會失敗,所以還要加以ajax去輔助,或者就是一個用戶會話分配一個識別碼,直到用戶會話結束。
有了這個過濾器之后我們就可以進行實際的測試了,首先我們先在Home控制器中寫入如下代碼,並在Views/Home下新建Index.cshtml以及List.cshtml,其中List.cshtml中的頁面如下所示:
1 @{ 2 ViewBag.Title = "List"; 3 } 4 5 <h2>List</h2> 6 @using(Html.BeginForm()) 7 { 8 @Html.Hidden("viewMac", (string)ViewBag.ViewMac) 9 <input type="submit" value="submit" /> 10 }
然后就是Home控制器:
1 namespace MvcStudy.Controllers 2 { 3 public class HomeController : Controller 4 { 5 [MobilResultFilter] 6 public ActionResult Index() 7 { 8 9 return View(); 10 } 11 12 13 public ActionResult List() 14 { 15 string viewMac = Guid.NewGuid().ToString(); 16 Session["vmac"] = viewMac; 17 ViewBag.ViewMac = viewMac; 18 return View(); 19 } 20 21 [ViewMacFilter] 22 [HttpPost] 23 public ActionResult List(string action) 24 { 25 return View(); 26 } 27 } 28 }
這里我們可以看到默認的List動作中會分配識別碼,而POST請求對應的List方法加上了我們之前寫的過濾器,現在我們可以先打開這頁面然后提交,會發現只是刷新了,接着我們把List.cshtml中的@Html.Hidden("viewMac", (string)ViewBag.ViewMac) 刪去,重新進入這個頁面,在點擊提交,就能發現出現了404的錯誤,這樣我們就可以防止跨站請求了。
4.結果過濾器
上面講的僅僅只是動作過濾器,在動作執行完成之后都會返回一個結果,即使到這里了,我們依然可以修改最終的結果,之前在講路由器部分的時候,曾今舉過一個例子,就是根據UserAgent去判斷是否為手機,並跳轉到對應的控制器下的對應動作。但是很多時候手機頁面與PC頁面的數據都是一摸一樣的,如果像之前那樣就會導致重復代碼的出現,那么我們就可以利用結果過濾器,在動作執行完成之后,判斷是否為手機從而改變最后呈現的視圖。
下面我們繼續在Filter文件夾下新建一個MobilResultFilterAttribute類,並且這個類依然要繼承FilterAttribute,同時還要實現IResultFilter接口,實現代碼如下:
1 namespace MvcStudy.Filter 2 { 3 public class MobilResultFilterAttribute : FilterAttribute , IResultFilter 4 { 5 6 public void OnResultExecuted(ResultExecutedContext filterContext) 7 { 8 9 } 10 11 public void OnResultExecuting(ResultExecutingContext filterContext) 12 { 13 if (filterContext.HttpContext.Request.UserAgent.Contains("Android")) 14 { 15 ((ViewResult)filterContext.Result).ViewName = "List"; 16 } 17 } 18 } 19 }
為了能夠以最簡單的方式說明,所以筆者僅僅判斷了UserAgent是否含有Android,如果含有就修改最終的視圖名稱,這樣我們就可以通過注解屬性的方式實現,而不需要放在控制器中或者在動作中進行判斷。
下面我們依然要以測試為主,我們還是使用Home控制器:
1 [MobilResultFilter] 2 public ActionResult Index() 3 { 4 5 return View(); 6 }
首先我們通過正常的方式訪問,然后讓Chrome模擬手機進行訪問,這個時候我們就可以發現最終的頁面會是List而不是Index了。
上面的3和4我們都要自己去繼承對應的接口,同時還要繼承FilterAttribute類,其實ASP.NET MVC中已經為我們寫好了一個默認的類,就是ActionFilterAttribute,我們源碼中的定義:
1 public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IresultFilter
它已經繼承了FilterAttribute,同時也實現了IActionFilter與IResultFilter接口,這樣可以可以少記一些關鍵的名稱。
5.無注解屬性過濾與全局過濾器
通過之前的幾點,大家可以發現我們都是使用注解屬性的方式進行過濾,其他我們也可以直接在控制器中實現,比如下面的代碼:
1 namespace MvcStudy.Controllers 2 { 3 public class HomeController : Controller 4 { 5 protected override void OnActionExecuted(ActionExecutedContext filterContext) 6 { 7 base.OnActionExecuted(filterContext); 8 } 9 10 protected override void OnActionExecuting(ActionExecutingContext filterContext) 11 { 12 base.OnActionExecuting(filterContext); 13 } 14 15 protected override void OnAuthorization(AuthorizationContext filterContext) 16 { 17 base.OnAuthorization(filterContext); 18 } 19 20 protected override void OnException(ExceptionContext filterContext) 21 { 22 base.OnException(filterContext); 23 } 24 25 protected override void OnResultExecuted(ResultExecutedContext filterContext) 26 { 27 base.OnResultExecuted(filterContext); 28 } 29 30 protected override void OnResultExecuting(ResultExecutingContext filterContext) 31 { 32 base.OnResultExecuting(filterContext); 33 } 34 35 [MobilResultFilter] 36 public ActionResult Index() 37 { 38 39 return View(); 40 } 41 42 43 public ActionResult List() 44 { 45 string viewMac = Guid.NewGuid().ToString(); 46 Session["vmac"] = viewMac; 47 ViewBag.ViewMac = viewMac; 48 return View(); 49 } 50 51 [ViewMacFilter] 52 [HttpPost] 53 public ActionResult List(string action) 54 { 55 return View(); 56 } 57 } 58 }
我們可以看到控制器中其實已經實現了上面所有的接口,當然我建議使用注解屬性,這樣可以盡量的分離關注點,控制器的代碼會很雜亂。
關於全局過濾器,唯一的區別就是我們需要在FilterConfig類中的RegisterGlobalFilters方法中使用filters.add增加上面我們所寫的過濾器即可,並沒有太大的特殊(FilterConfig在App_Start,在ASP.NET MVC 4以上才有這個文件)
6.內建過濾器
關於內建過濾器我們這里就列舉出來以下,因為上手很快。
RequireHttps 強迫對動作使用Https協議
OutputCache 緩存一個動作方法的輸出
ValidateInput 與安全有關的授權過濾器
AsyncTimeout/NoAsyncTimeout 用於異步控制器
ChildActionOnly 只能作為子操作進行調用
到此為止關於過濾器部分就結束了。
下面是之前的前面ASP.NET MVC學習連載: