1. 程式人生 > >ASP.NET MVC Filters 4種默認過濾器的使用【附示例】

ASP.NET MVC Filters 4種默認過濾器的使用【附示例】

rac model 指定 host 會話 決策 蜘蛛 formate 操作性

過濾器(Filters)的出現使得我們可以在ASP.NET MVC程序裏更好的控制瀏覽器請求過來的URL,不是每個請求都會響應內容,只響應特定內容給那些有特定權限的用戶,過濾器理論上有以下功能:

  1. 判斷登錄與否或用戶權限
  2. 決策輸出緩存
  3. 防盜鏈
  4. 防蜘蛛
  5. 本地化與國際化設置
  6. 實現動態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種默認過濾器的使用【附示例】