ASP.NET MVC Filters 4種默認過濾器的使用【附示例】
過濾器(Filters)的出現使得我們可以在ASP.NET MVC程序裏更好的控制瀏覽器請求過來的URL,不是每個請求都會響應內容,只響應特定內容給那些有特定權限的用戶,過濾器理論上有以下功能:
- 判斷登錄與否或用戶權限
- 決策輸出緩存
- 防盜鏈
- 防蜘蛛
- 本地化與國際化設置
- 實現動態Action(做權限管理系統的好東西)
先來看一個簡單的例子:新建一個AuthFiltersController,裏面有兩個Action
public ActionResult Index() { return View(); } [Authorize] public ActionResult Welcome() { return View(); }
很顯然,第一個名為Index的Action是沒有過濾的,任何身份的請求都可以通過,只要在瀏覽器的URL欄裏鍵入:localhost:****/AuthFilters/Index 就能得到對應的視圖響應;
而第二個名為Welcome的Action上面標註了[Authorize],表示這是一個只處理那些通過身份驗證的URL請求,如果沒有通過身份驗證就請求這個Action會被帶到登錄頁面。看看配置文件:
<authentication mode="Forms"> <forms loginUrl="~/Account/LogOn" timeout="2880" /> </authentication>
根據配置文件的定義,登錄頁面就是AccountController下名為LogOn的Action,那麽就新建一個AccountController,並新建兩個Action:
public ActionResult LogOn() { return View(); } [HttpPost] public ActionResult LogOn(LogOnViewModel model) { if (model.UserName.Trim() == model.Password.Trim()) //偽代碼,只要輸入的用戶名和密碼一樣就過 { if (model.RememberMe) FormsAuthentication.SetAuthCookie(model.UserName, true); //2880分鐘有效期的cookie else FormsAuthentication.SetAuthCookie(model.UserName, false); //會話cookie return RedirectToAction("Welcome", "AuthFilters"); } else return View(model); }
第一個是處理Get請求用於響應視圖頁面的,第二個是處理用戶點擊提交回發的登錄表單。
LogOnViewModel是用戶登錄實體類,看具體定義:
/// <summary> /// 用戶登錄類 /// </summary> public class LogOnViewModel { /// <summary> /// 用戶名 /// </summary> public string UserName { get; set; } /// <summary> /// 密碼 /// </summary> public string Password { get; set; } /// <summary> /// 記住我 /// </summary> public bool RememberMe { get; set; } }
ok,按F6編譯下項目,再按Ctrl + F5運行下項目,在URL裏輸入:localhost:****/AuthFilters/Index 很輕松的得到了Index這個Action的響應
再定位到:localhost:****/AuthFilters/Welcome
可見,雖然定位到了Welcome這個Action,但是卻並不像Index一樣直接返回對應的視圖,而是被帶到了登錄頁面。就是因為Welcome這個Action上被標註了[Authorize],拒絕了所以未驗證用戶的訪問。
既然拒絕了未驗證的用戶,那就登錄下通過驗證,看看上面LogOn裏寫的偽代碼就知道,輸入相同的用戶名和密碼就能登錄成功,用戶名和密碼都輸入“wangjie”試試:
已經通過驗證並得到Welcome這個Action的響應了。相比之前就是多生成了一個名為“.ASPXAUTH”的Cookie,這是個默認名,配置文件裏可以修改。同時,如果登錄的時候勾選了“記住我”那麽此Cookie的過期時間就是配置文件裏定義的2880分鐘。
ok,現在提高下難度,只設置名為“a”、“bb”、“ccc”的用戶可以訪問歡迎頁面:
[Authorize(Users = "a,bb,ccc")] public ActionResult Welcome() { ViewBag.Message = "已登錄"; return View(); }
再用“wangjie”登錄下發現跳不到歡迎頁面了,因為指定了a、bb、ccc這三個用戶才可以登錄。
先不管為何在Action上標註Users = "a,bb,ccc"就可以控制可以訪問的用戶,但從操作性上來說這樣控制Action的訪問權限還是很方便的。但是如果項目大,用戶對應的角色和權限變化比較大,每次變化都來重新標註Action顯然不合適。MVC框架提供的過濾器(Filters)就派上了用場:
上圖是Asp.Net MVC框架提供的幾種默認Filter:授權篩選器、操作篩選器、結果篩選器、異常篩選器,下面來一一講解,先看演示Demo結構圖:
一、授權篩選器
授權篩選器用於實現IAuthorizationFilter接口和做出關於是否執行操作方法(如執行身份驗證或驗證請求的屬性)的安全決策。 AuthorizeAttribute類和RequireHttpsAttribute類是授權篩選器的示例。授權篩選器在任何其他篩選器之前運行。
新建一個繼承AuthorizeAttribute類的UserAuthorize類,F12定位到AuthorizeAttribute類,看看內部申明:
public AuthorizeAttribute(); public string Roles { get; set; } public override object TypeId { get; } public string Users { get; set; } protected virtual bool AuthorizeCore(HttpContextBase httpContext); protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext); public virtual void OnAuthorization(AuthorizationContext filterContext); protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext);
上面演示的指定用戶才可以訪問就是利用了Users屬性,並由基類幫助我們驗證,只放指定的Users用戶通過。要實現自定義的驗證只需重寫下OnAuthorization和AuthorizeCore方法。為了演示效果,新建一個SampleData類用來初始化數據:
/// <summary> /// 測試數據(實際項目中,這些數據應該從數據庫拿) /// </summary> public class SampleData { public static List<User> users; public static List<Role> roles; public static List<RoleWithControllerAction> roleWithControllerAndAction; static SampleData() { // 初始化用戶 users = new List<User>(){ new User(){ Id=1, UserName="wangjie", RoleId=1}, new User(){ Id=2, UserName ="senior1", RoleId=2}, new User(){ Id=3, UserName ="senior2", RoleId=2}, new User(){ Id=5, UserName="junior1", RoleId=3}, new User(){ Id=6, UserName="junior2", RoleId=3}, new User(){ Id=6, UserName="junior3", RoleId=3} }; // 初始化角色 roles = new List<Role>() { new Role() { Id=1, RoleName="管理員", Description="管理員角色"}, new Role() { Id=2, RoleName="高級會員", Description="高級會員角色"}, new Role() { Id=3, RoleName="初級會員", Description="初級會員角色"} }; // 初始化角色控制器和Action對應類 roleWithControllerAndAction = new List<RoleWithControllerAction>() { new RoleWithControllerAction(){ Id=1, ControllerName="AuthFilters", ActionName="AdminUser", RoleIds="1"}, new RoleWithControllerAction(){ Id=2, ControllerName="AuthFilters", ActionName="SeniorUser", Ids="1,2"}, new RoleWithControllerAction(){ Id=3, ControllerName="AuthFilters", ActionName="JuniorUser", Ids="1,2,3"}, new RoleWithControllerAction(){ Id=4, ControllerName="ActionFilters", ActionName="Index", RoleIds="2,3"} }; } }
簡單明了,用戶擁有角色,不同角色可以訪問的Action也不同。這比較符合權限項目裏的控制。再看看UserAuthorize類的具體定義:
/// <summary> /// 自定義用戶授權 /// </summary> public class UserAuthorize : AuthorizeAttribute { /// <summary> /// 授權失敗時呈現的視圖 /// </summary> public string AuthorizationFailView { get; set; } /// <summary> /// 請求授權時執行 /// </summary> public override void OnAuthorization(AuthorizationContext filterContext) { //獲得url請求裏的controller和action: string controllerName = filterContext.RouteData.Values["controller"].ToString().ToLower(); string actionName = filterContext.RouteData.Values["action"].ToString().ToLower(); //string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; //string actionName = filterContext.ActionDescriptor.ActionName; //根據請求過來的controller和action去查詢可以被哪些角色操作: Models.RoleWithControllerAction roleWithControllerAction = base.SampleData.roleWithControllerAndAction.Find(r => r.ControllerName.ToLower() == controllerName && tionName.ToLower() == actionName); if (roleWithControllerAction != null) { this.Roles = roleWithControllerAction.RoleIds; //有權限操作當前控制器和Action的角色id } base.OnAuthorization(filterContext); //進入AuthorizeCore } /// <summary> /// 自定義授權檢查(返回False則授權失敗) /// </summary> protected override bool AuthorizeCore(HttpContextBase httpContext) { if (httpContext.User.Identity.IsAuthenticated) { string userName = httpContext.User.Identity.Name; //當前登錄用戶的用戶名 Models.User user = Database.SampleData.users.Find(u => u.UserName == userName); //當前登錄用戶對象 if (user != null) { Models.Role role = Database.SampleData.roles.Find(r => r.Id == user.RoleId); //當前登錄用戶的角色 foreach (string roleid in Roles.Split(‘,‘)) { if (role.Id.ToString() == roleid) return true; } return false; } else return false; } else return false; //進入HandleUnauthorizedRequest } /// <summary> /// 處理授權失敗的HTTP請求 /// </summary> protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { filterContext.Result = new ViewResult { ViewName = AuthorizationFailView }; } }
自定義好授權類就可以到控制器上使用了,看看AuthFiltersController類:
public class AuthFiltersController : Controller { public ActionResult Index() { return View(); } //[Authorize(Users = "a,bb,ccc")] [Authorize] public ActionResult Welcome() { ViewBag.Message = "普通已授權頁面"; return View(); } [UserAuthorize(AuthorizationFailView = "Error")] //管理員頁面 public ActionResult AdminUser() { ViewBag.Message = "管理員頁面"; return View("Welcome"); } [UserAuthorize(AuthorizationFailView = "Error")] //會員頁面(管理員、會員都可訪問) public ActionResult SeniorUser() { ViewBag.Message = "高級會員頁面"; return View("Welcome"); } [UserAuthorize(AuthorizationFailView = "Error")] //遊客頁面(管理員、會員、遊客都可訪問) public ActionResult JuniorUser() { ViewBag.Message = "初級會員頁面"; return View("Welcome"); } }
Welcome這個Action使用了默認的授權驗證,只要登陸成功就可以訪問。其他幾個Action上都標註了自定義的UserAuthorize,並沒有標註Users="....",Roles=".....",因為這樣在Action上寫死用戶或者角色控制權限顯然是不可行的,用戶和角色的對應以及不同的角色可以操作的Action應該是從數據庫裏取出來的。為了演示就在SampleData類裏初始化了一些用戶和角色信息,根據SampleData類的定義,很明顯wangjie擁有1號管理員角色,可以訪問AuthFilters這個控制器下的所有Action,senior1、senior2擁有2號高級會員的角色,可以訪問AuthFilters這個控制器下除了AdminUser之外的Action等等
再次登陸下,就發現擁有高級會員角色的用戶senior1是不可以訪問AdminUser這個Action,會被帶到AuthorizationFailView屬性指定的Error視圖:
二、操作篩選器、結果篩選器
操作篩選器用於實現IActionFilter接口以及包裝操作方法執行。IActionFilter接口聲明兩個方法:OnActionExecuting和OnActionExecuted。OnActionExecuting在操作方法之前運行。OnActionExecuted在操作方法之後運行,可以執行其他處理,如向操作方法提供額外數據、檢查返回值或取消執行操作方法。
結果篩選器用於實現IResultFilter接口以及包裝ActionResult對象的執行。IResultFilter接口聲明兩個方法OnResultExecuting和OnResultExecuted。OnResultExecuting在執行ActionResult對象之前運行。OnResultExecuted在結果之後運行,可以對結果執行其他處理,如修改 HTTP 響應。OutputCacheAttribute 類是結果篩選器的一個示例。
操作篩選器和結果篩選器都實現ActionFilterAttribute類,看看類裏定義的方法:
public virtual void OnActionExecuted(ActionExecutedContext filterContext); public virtual void OnActionExecuting(ActionExecutingContext filterContext); public virtual void OnResultExecuted(ResultExecutedContext filterContext); public virtual void OnResultExecuting(ResultExecutingContext filterContext);
根據方法的名字就知道4個方法執行的順序了:
OnActionExecuting是Action執行前的操作、OnActionExecuted則是Action執行後的操作、OnResultExecuting是解析ActionResult前執行、OnResultExecuted是解析ActionResult後執行
即:Action執行前:OnActionExecuting方法先執行→Action執行 →OnActionExecuted方法執行→OnResultExecuting方法執行→返回的ActionRsult中的 executeResult方法執行→OnResultExecuted執行
完全可以重寫OnActionExecuting方法實現上面授權篩選器一樣的功能,因為OnActionExecuting方法是在Action方法執行前運行的,自定義一個實現ActionFilterAttribute類的ActionFilters類,OnActionExecuting方法這麽寫:
/// <summary> /// 在執行操作方法之前由 MVC 框架調用 /// </summary> public override void OnActionExecuting(ActionExecutingContext filterContext) { string userName = filterContext.HttpContext.User.Identity.Name; //當前登錄用戶的用戶名 Models.User user = Database.SampleData.users.Find(u => u.UserName == userName); //當前登錄用戶對象 if (user != null) { Models.Role role = Database.SampleData.roles.Find(r => r.Id == user.RoleId); //當前登錄用戶的角色 //獲得controller: string controllerName = filterContext.RouteData.Values["controller"].ToString().ToLower(); //string actionName = filterContext.RouteData.Values["action"].ToString().ToLower(); if (ActionName == null) ActionName = filterContext.RouteData.Values["action"].ToString(); //查詢角色id Models.RoleWithControllerAction roleWithControllerAction = .SampleData.roleWithControllerAndAction.Find(r => r.ControllerName.ToLower() == controllerName && Name.ToLower() == ActionName.ToLower()); if (roleWithControllerAction != null) { this.Roles = roleWithControllerAction.RoleIds; //有權限操作當前控制器和Action的角色id } if (!string.IsNullOrEmpty(Roles)) { foreach (string roleid in Roles.Split(‘,‘)) { if (role.Id.ToString() == roleid) return; //return就說明有權限了,後面的代碼就不跑了,直接返回視圖給瀏覽器就好 } } filterContext.Result = new EmptyResult(); //請求失敗輸出空結果 HttpContext.Current.Response.Write("對不起,你沒有權限!"); //打出提示文字 //return; } else { //filterContext.Result = new ViewResult { ViewName = "Error" }; filterContext.Result = new EmptyResult(); HttpContext.Current.Response.Write("對不起,請先登錄!"); //return; } //base.OnActionExecuting(filterContext); }
看看如何在ActionFiltersController控制器裏使:
public class ActionFiltersController : Controller { [ActionFilters] public ActionResult Index() { return View(); } [ActionFilters(ActionName = "Index")] public ActionResult Details() { return View(); } [ActionFilters] public ActionResult Test() { return View(); } }
很明顯Index和Details這兩個Action同用一個權限,看看初始化數據SampleData類的定義:
new RoleWithControllerAction(){ Id=4, ControllerName="ActionFilters", ActionName="Index", RoleIds="2,3"}
只有2和3號角色可以訪問,那麽1號角色的wangjie用戶應該是訪問不了的,登錄試試:
三、異常篩選器
異常篩選器用於實現IExceptionFilter接口,並在ASP.NET MVC管道執行期間引發了未處理的異常時執行。異常篩選器可用於執行諸如日誌記錄或顯示錯誤頁之類的任務。HandleErrorAttribute類是異常篩選器的一個示例。
有之前授權篩選器、操作和結果篩選器的使用經驗,再看異常篩選器就簡單許多了,來看看自定義的繼承自HandleErrorAttribute類的異常篩選類ExceptionFilters:
/// <summary> /// 異常篩選器 /// </summary> public class ExceptionFilters : HandleErrorAttribute { /// <summary> /// 在發生異常時調用 /// </summary> public override void OnException(ExceptionContext filterContext) { //if (!filterContext.ExceptionHandled && filterContext.Exception is NullReferenceException) if (!filterContext.ExceptionHandled) { //獲取出現異常的controller名和action名,用於記錄 string controllerName = (string)filterContext.RouteData.Values["controller"]; string actionName = (string)filterContext.RouteData.Values["action"]; //定義一個HandErrorInfo,用於Error視圖展示異常信息 HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName); ViewResult result = new ViewResult { ViewName = this.View, ViewData = new ViewDataDictionary<HandleErrorInfo>(model) //定義ViewData,泛型 }; filterContext.Result = result; filterContext.ExceptionHandled = true; } //base.OnException(filterContext); } }
看看如何在視圖中使用:
[ExceptionFilters(View = "Exception")] public ActionResult Index() { throw new NullReferenceException("測試拋出異常!"); }
View是制定的捕獲異常後顯示給用戶的視圖:
再看一個Action:
[ExceptionFilters(View = "ExceptionDetails")] public ActionResult Details() { int i = int.Parse("hello,world!"); return View(); }
把string類型的數據強轉int,肯定得報FormatException異常,看看ExceptionDetails視圖如何定義的:
@model System.Web.Mvc.HandleErrorInfo @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>異常</title> </head> <body> <p> 拋錯控制器:<b>@Model.ControllerName</b> 拋錯方法:<b>@Model.ActionName</b> 拋錯類型:<b>@Model.Exception.GetType ().Name</b> </p> <p> 異常信息:<b>@Model.Exception.Message</b> </p> <p> 堆棧信息:</p> <pre>@Model.Exception.StackTrace</pre> </body> </html>
瀏覽器顯示結果:
轉載自:http://www.360doc.com/showweb/0/0/765540458.aspx
ASP.NET MVC Filters 4種默認過濾器的使用【附示例】