[ASP.NET MVC 小牛之路]11 - Filter

Filter(篩選器)是基於AOP(面向切面編程)的設計,它的做用是對MVC框架處理客戶端請求注入額外的邏輯,以很是簡單優美的方式實現橫切關注點(Cross-cutting Concerns)。橫切關注點是指橫越應該程序的多個甚至全部模塊的功能,經典的橫切關注點有日誌記錄、緩存處理、異常處理和權限驗證等。本文將分別介紹MVC框架所支持的不一樣種類的Filter的建立和使用,以及如何控制它們的執行。html

本文目錄web

四種基本 Filter 概述

MVC框架支持的Filter能夠歸爲四類,每一類均可以對處理請求的不一樣時間點引入額外的邏輯處理。這四類Filter以下表:編程

在MVC框架調用acion以前,它會先判斷有沒有實現上表中的接口的特性,若是有,則在請求管道的適當的點調用特性中定義的方法。瀏覽器

MVC框架爲這些種類的Filter接口實現了默認的特性類。如上表,ActionFilterAttribute 類實現了 IActionFilter 和 IResultFilter 兩個接口,這個類是一個抽象類,必須對它提供實現。另外兩個特性類,AuthorizeAttribute 和 HandleErrorAttribute, 已經提供了一些有用的方法,能夠直接使用。緩存

Filter 既能應用在單個的ation方法上,也能應用在整個controller上,並能夠在acion和controller上應用多個Filter。以下所示:安全

[Authorize(Roles="trader")]  // 對全部action有效
public class ExampleController : Controller { 
 
    [ShowMessage]  // 對當前ation有效
    [OutputCache(Duration=60)] // 對當前ation有效
    public ActionResult Index() { 
        // ...  
    } 
}

注意,對於自定義的controller的基類,應用於該基類的Filter也將對繼承自該基類的全部子類有效。框架

Authorization Filter

Authorization Filter是在action方法和其餘種類的Filter以前運行的。它的做用是強制實施權限策略,保證action方法只能被受權的用戶調用。Authorization Filter實現的接口以下:異步

namespace System.Web.Mvc {
    public interface IAuthorizationFilter { 
        void OnAuthorization(AuthorizationContext filterContext); 
    } 
} 

自定義Authorization Filter

你能夠本身實現 IAuthorizationFilter 接口來建立本身的安全認證邏輯,但通常沒有這個必要也不推薦這樣作。若是要自定義安全認證策略,更安全的方式是繼承默認的 AuthorizeAttribute 類。ide

咱們下面經過繼承 AuthorizeAttribute 類來演示自定義Authorization Filter。新建一個空MVC應用程序,和往常的示例同樣添加一個 Infrastructure 文件夾,而後添加一個 CustomAuthAttribute.cs 類文件,代碼以下:函數

namespace MvcApplication1.Infrastructure {
    public class CustomAuthAttribute : AuthorizeAttribute {
        private bool localAllowed;
        public CustomAuthAttribute(bool allowedParam) {
            localAllowed = allowedParam;
        }
        protected override bool AuthorizeCore(HttpContextBase httpContext) {
            if (httpContext.Request.IsLocal) {
                return localAllowed;
            }
            else {
                return true;
            }
        }
    }
}

這個簡單的Filter,經過重寫 AuthorizeCore 方法,容許咱們阻止本地的請求,在應用該Filter時,能夠經過構造函數來指定是否容許本地請求。AuthorizeAttribte 類幫咱們內置地實現了不少東西,咱們只需把重點放在 AuthorizeCore 方法上,在該方法中實現權限認證的邏輯。

爲了演示這個Filter的做用,咱們新建一個名爲 Home 的 controller,而後在 Index action方法上應用這個Filter。參數設置爲false以保護這個 action 不被本地訪問,以下:

public class HomeController : Controller {

    [CustomAuth(false)]
    public string Index() {
        return "This is the Index action on the Home controller";
    }
}

運行程序,根據系統生成的默認路由值,將請求 /Home/Index,結果以下:

咱們經過把 AuthorizeAttribute 類做爲基類自定義了一個簡單的Filter,那麼 AuthorizeAttribute 類自己做爲Filter有哪些有用的功能呢?

使用內置的Authorization Filter

當咱們直接使用 AuthorizeAttribute 類做爲Filter時,能夠經過兩個屬性來指定咱們的權限策略。這兩個屬性及說明以下:

  • Users屬性,string類型,指定容許訪問action方法的用戶名,多個用戶名用逗號隔開。
  • Roles屬性,string類型,用逗號分隔的角色名,訪問action方法的用戶必須屬於這些角色之一。

使用以下:

public class HomeController : Controller {

    [Authorize(Users = "jim, steve, jack", Roles = "admin")]
    public string Index() {
        return "This is the Index action on the Home controller";
    }
}

這裏咱們爲Index方法應用了Authorize特性,並同時指定了能訪問該方法的用戶和角色。要訪問Index action,必須二者都知足條件,即用戶名必須是 jim, steve, jack 中的一個,並且必須屬性 admin 角色。

另外,若是不指定任何用戶名和角色名(即 [Authorize] ),那麼只要是登陸用戶都能訪問該action方法。

你能夠經過建立一個Internet模板的應用程序來看一下效果,這裏就不演示了。

對於大部分應用程序,AuthorizeAttribute 特性類提供的權限策略是足夠用的。若是你有特殊的需求,則能夠經過繼承AuthorizeAttribute 類來知足。

Exception Filter

Exception Filter,在下面三種來源拋出未處理的異常時運行:

  • 另一種Filter(如Authorization、Action或Result等Filter)。
  • Action方法自己。
  • Action方法執行完成(即處理ActionResult的時候)。

Exception Filter必須實現 IExceptionFilter 接口,該接口的定義以下:

namespace System.Web.Mvc { 
    public interface IExceptionFilter { 
        void OnException(ExceptionContext filterContext); 
    } 
} 

ExceptionContext 經常使用屬性說明

在 IExceptionFilter 的接口定義中,惟一的 OnException 方法在未處理的異常引起時執行,其中參數的類型:ExceptionContext,它繼承自 ControllerContext 類,ControllerContext 包含以下經常使用的屬性:

  • Controller,返回當前請求的controller對象。
  • HttpContext,提供請求和響應的詳細信息。
  • IsChildAction,若是是子action則返回true(稍後將簡單介紹子action)。
  • RequestContext,提供請求上下文信息。
  • RouteData,當前請求的路由實例信息。

做爲繼承 ControllerContext 類的子類,ExceptionContext 類還提供瞭如下對處理異常的經常使用屬性:

  • ActionDescriptor,提供action方法的詳細信息。
  • Result,是一個 ActionResult 類型,經過把這個屬性值設爲非空可讓某個Filter的執行取消。
  • Exception,未處理異常信息。
  • ExceptionHandled,若是另一個Filter把這個異常標記爲已處理則返回true。

一個Exception Filter能夠經過把 ExceptionHandled 屬性設置爲true來標註該異常已被處理過,這個屬性通常在某個action方法上應用了多個Exception Filter時會用到。ExceptionHandled 屬性設置爲true後,就能夠經過該屬性的值來判斷其它應用在同一個action方法Exception Filter是否已經處理了這個異常,以避免同一個異常在不一樣的Filter中重複被處理。

示例演示

在 Infrastructure 文件夾下添加一個 RangeExceptionAttribute.cs 類文件,代碼以下:

public class RangeExceptionAttribute : FilterAttribute, IExceptionFilter {
    public void OnException(ExceptionContext filterContext) {
        if (!filterContext.ExceptionHandled && filterContext.Exception is ArgumentOutOfRangeException) {
            filterContext.Result = new RedirectResult("~/Content/RangeErrorPage.html");
            filterContext.ExceptionHandled = true;
        }
    }
}

這個Exception Filter經過重定向到Content目錄下的一個靜態html文件來顯示友好的 ArgumentOutOfRangeException 異常信息。咱們定義的 RangeExceptionAttribute 類繼承了FilterAttribute類,而且實現了IException接口。做爲一個MVC Filter,它的類必須實現IMvcFilter接口,你能夠直接實現這個接口,但更簡單的方法是繼承 FilterAttribute 基類,該基類實現了一些必要的接口並提供了一些有用的基本特性,好比按照默認的順序來處理Filter。

在Content文件夾下面添加一個名爲RangeErrorPage.html的文件用來顯示友好的錯誤信息。以下所示:

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
    <title>Range Error</title> 
</head> 
<body> 
    <h2>Sorry</h2> 
    <span>One of the arguments was out of the expected range.</span> 
</body> 
</html>

在HomeController中添加一個值越限時拋出異常的action,以下所示:

    public class HomeController : Controller { 
        [RangeException]
        public string RangeTest(int id) { 
            if (id > 100) { 
                return String.Format("The id value is: {0}", id); 
            } else { 
                throw new ArgumentOutOfRangeException("id", id, ""); 
            } 
        } 
    } 

當對RangeTest應用自定義的的Exception Filter時,運行程序URL請求爲 /Home/RangeTest/50,程序拋出異常後將重定向到RangeErrorPage.html頁面:

因爲靜態的html文件是和後臺脫離的,因此實際項目中更多的是用一個View來呈現友好的錯誤信息,以便很好的對它進行一些動態的控制。以下面把示例改動一下,RangeExceptionAttribute 類修改以下:

    public class RangeExceptionAttribute : FilterAttribute, IExceptionFilter {
        public void OnException(ExceptionContext filterContext) {
            if (!filterContext.ExceptionHandled && filterContext.Exception is ArgumentOutOfRangeException) {
                int val = (int)(((ArgumentOutOfRangeException)filterContext.Exception).ActualValue); filterContext.Result = new ViewResult { ViewName = "RangeError", ViewData = new ViewDataDictionary<int>(val) };
                filterContext.ExceptionHandled = true;
            }
        }
    }

咱們建立一個ViewResult對象,指定了發生異常時要重定向的View名稱和傳遞的model對象。而後咱們在Views/Shared文件夾下添加一個RangeError.cshtml文件,代碼以下:

@model int

<!DOCTYPE html> 
<html> 
<head> 
    <meta name="viewport" content="width=device-width" /> 
    <title>Range Error</title> 
</head> 
<body> 
    <h2>Sorry</h2> 
    <span>The value @Model was out of the expected range.</span> 
    <div> 
        @Html.ActionLink("Change value and try again", "Index") 
    </div> 
</body> 
</html> 

運行結果以下:

禁用異常跟蹤

不少時候異常是不可預料的,在每一個Action方法或Controller上應用Exception Filter是不現實的。並且若是異常出如今View中也沒法應用Filter。如RangeError.cshtml這個View加入下面代碼:

@model int

@{ 
    var count = 0; 
    var number = Model / count; 
} 

...

運行程序後,將會顯示以下信息:

顯然程序發佈後不該該顯示這些信息給用戶看。咱們能夠經過配置Web.config讓應用程序無論在什麼時候何地引起了異常均可以顯示統一的友好錯誤信息。在Web.config文件中的<system.web>節點下添加以下子節點:

  <system.web>
    
    ...
    <customErrors mode="On" defaultRedirect="/Content/RangeErrorPage.html"/>
  </system.web>

這個配置只對遠程訪問有效,本地運行站點依然會顯示跟蹤信息。

使用內置的 Exceptin Filter

經過上面的演示,咱們理解了Exceptin Filter在MVC背後是如何運行的。但咱們並不會常常去建立本身的Exceptin Filter,由於微軟在MVC框架中內置的 HandleErrorAttribute(實現了IExceptionFilter接口) 已經足夠咱們平時使用。它包含ExceptionType、View和Master三個屬性。當ExceptionType屬性指定類型的異常被引起時,這個Filter將用View屬性指定的View(使用默認的Layout或Mast屬性指定的Layout)來呈現一個頁面。以下面代碼所示:

... 
[HandleError(ExceptionType = typeof(ArgumentOutOfRangeException), View = "RangeError")] 
public string RangeTest(int id) { 
    if (id > 100) { 
        return String.Format("The id value is: {0}", id); 
    } else { 
        throw new ArgumentOutOfRangeException("id", id, ""); 
    } 
} 
... 

使用內置的HandleErrorAttribute,將異常信息呈現到View時,這個特性同時會傳遞一個HandleErrorInfo對象做爲View的model。HandleErrorInfo類包含ActionName、ControllerName和Exception屬性,以下面的 RangeError.cshtml 使用這個model來呈現信息:

@model HandleErrorInfo 
@{ 
    ViewBag.Title = "Sorry, there was a problem!"; 
} 
 
<!DOCTYPE html> 
<html> 
<head> 
    <meta name="viewport" content="width=device-width" /> 
    <title>Range Error</title> 
</head> 
<body> 
    <h2>Sorry</h2> 
    <span>The value @(((ArgumentOutOfRangeException)Model.Exception).ActualValue) 
        was out of the expected range.</span>         
    <div> 
        @Html.ActionLink("Change value and try again", "Index") 
    </div> 
    <div style="display: none"> 
        @Model.Exception.StackTrace 
    </div> 
</body> 
</html>

Action Filter

顧名思義,Action Filter是對action方法的執行進行「篩選」的,包括執行前和執行後。它須要實現 IActionFilter 接口,該接口定義以下:

namespace System.Web.Mvc { 
    public interface IActionFilter { 
        void OnActionExecuting(ActionExecutingContext filterContext); 
        void OnActionExecuted(ActionExecutedContext filterContext); 
    } 
}

其中,OnActionExecuting方法在action方法執行以前被調用,OnActionExecuted方法在action方法執行以後被調用。咱們來看一個簡單的例子。
在Infrastructure文件夾下添加一個ProfileActionAttribute類,代碼以下:

using System.Diagnostics; 
using System.Web.Mvc; 

namespace MvcApplication1.Infrastructure { 
    public class ProfileActionAttribute : FilterAttribute, IActionFilter { 
        private Stopwatch timer; 
        public void OnActionExecuting(ActionExecutingContext filterContext) { 
            timer = Stopwatch.StartNew(); 
        } 
        public void OnActionExecuted(ActionExecutedContext filterContext) { 
            timer.Stop();
            if (filterContext.Exception == null) { 
                filterContext.HttpContext.Response.Write( 
                    string.Format("<div>Action method elapsed time: {0}</div>", timer.Elapsed.TotalSeconds)); 
            } 
        } 
    } 
} 

在HomeController中添加一個Action並應用該Filter,以下:

... 
[ProfileAction] 
public string FilterTest() { 
    return "This is the ActionFilterTest action"; 
} 
...

運行程序,URL定位到/Home/FilterTest,結果以下:

咱們看到,ProfileAction的 OnActionExecuted 方法是在 FilterTest 方法返回結果以前執行的。確切的說,OnActionExecuted 方法是在action方法執行結束以後和處理action返回結果以前執行的。

OnActionExecuting方法和OnActionExecuted方法分別接受ActionExecutingContext和ActionExecutedContext對象參數,這兩個參數包含了ActionDescriptor、Canceled、Exception等經常使用屬性。

Result Filter

Result Filter用來處理action方法返回的結果。用法和Action Filter相似,它須要實現 IResultFilter 接口,定義以下:

namespace System.Web.Mvc { 
    public interface IResultFilter { 
        void OnResultExecuting(ResultExecutingContext filterContext); 
        void OnResultExecuted(ResultExecutedContext filterContext); 
    } 
} 

IResultFilter 接口和以前的 IActionFilter 接口相似,要注意的是Result Filter是在Action Filter以後執行的。二者用法是同樣的,再也不多講,直接給出示例代碼。

在Infrastructure文件夾下再添加一個 ProfileResultAttribute.cs 類文件,代碼以下:

public class ProfileResultAttribute : FilterAttribute, IResultFilter { 
    private Stopwatch timer; 
    public void OnResultExecuting(ResultExecutingContext filterContext) { 
        timer = Stopwatch.StartNew(); 
    } 
    public void OnResultExecuted(ResultExecutedContext filterContext) { 
        timer.Stop(); 
        filterContext.HttpContext.Response.Write( 
            string.Format("<div>Result elapsed time: {0}</div>",  timer.Elapsed.TotalSeconds)); 
    } 
}

應用該Filter:

... 
[ProfileAction] 
[ProfileResult] 
public string FilterTest() { 
    return "This is the ActionFilterTest action"; 
} 
...

內置的 Action 和 Result Filter

MVC框架內置了一個 ActionFilterAttribute 類用來建立action 和 result 篩選器,便可以控制action方法的執行也能夠控制處理action方法返回結果。它是一個抽象類,定義以下:

public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IResultFilter{ 
        public virtual void OnActionExecuting(ActionExecutingContext filterContext) { 
        } 
        public virtual void OnActionExecuted(ActionExecutedContext filterContext) { 
        } 
        public virtual void OnResultExecuting(ResultExecutingContext filterContext) { 
        } 
        public virtual void OnResultExecuted(ResultExecutedContext filterContext) { 
        } 
    } 
}

使用這個抽象類方便之處是你只須要實現須要加以處理的方法。其餘和使用 IActionFilter 和 IResultFilter 接口沒什麼不一樣。下面是簡單作個示例。

在Infrastructure文件夾下添加一個 ProfileAllAttribute.cs 類文件,代碼以下:

public class ProfileAllAttribute : ActionFilterAttribute { 
    private Stopwatch timer; 
    public override void OnActionExecuting(ActionExecutingContext filterContext) { 
        timer = Stopwatch.StartNew(); 
    } 
    public override void OnResultExecuted(ResultExecutedContext filterContext) { 
        timer.Stop(); 
        filterContext.HttpContext.Response.Write(
        string.Format("<div>Total elapsed time: {0}</div>",  timer.Elapsed.TotalSeconds)); 
    } 
}

在HomeController中的FilterTest方法上應用該Filter:

... 
[ProfileAction] 
[ProfileResult] 
[ProfileAll] public string FilterTest() { 
    return "This is the FilterTest action"; 
} 
...

運行程序,URL定位到/Home/FilterTest,能夠看到一個Action從執行以前到結果處理完畢總共花的時間:

咱們也能夠Controller中直接重寫 ActionFilterAttribute 抽象類中定義的四個方法,效果和使用Filter是同樣的,例如:

public class HomeController : Controller { 
    private Stopwatch timer; 
    ...
    public string FilterTest() { 
        return "This is the FilterTest action"; 
    } 
    protected override void OnActionExecuting(ActionExecutingContext filterContext) { 
        timer = Stopwatch.StartNew(); 
    } 
    protected override void OnResultExecuted(ResultExecutedContext filterContext) { 
        timer.Stop(); 
        filterContext.HttpContext.Response.Write( 
            string.Format("<div>Total elapsed time: {0}</div>", 
            timer.Elapsed.TotalSeconds)); 
    } 
} 

註冊爲全局 Filter

全局Filter對整個應用程序的全部controller下的全部action方法有效。在App_Start/FilterConfig.cs文件中的RegisterGlobalFilters方法,能夠把一個Filter類註冊爲全局,如:

using System.Web; 
using System.Web.Mvc; 
using MvcApplication1.Infrastructure; 
 
namespace MvcApplication1 { 
    public class FilterConfig { 
        public static void RegisterGlobalFilters(GlobalFilterCollection filters) { 
            filters.Add(new HandleErrorAttribute()); 
            filters.Add(new ProfileAllAttribute()); 
        } 
    } 
}

咱們增長了filters.Add(new ProfileAllAttribute())這行代碼,其中的filters參數是一個GlobalFilterCollection類型的集合。爲了驗證 ProfileAllAttribute 應用到了全部action,咱們另外新建一個controller並添加一個簡單的action,以下:

public class CustomerController : Controller { 
        public string Index() { 
            return "This is the Customer controller"; 
        } 
}

運行程序,將URL定位到 /Customer ,結果以下:

其它經常使用 Filter

MVC框架內置了不少Filter,常見的有RequireHttps、OutputCache、AsyncTimeout等等。下面例舉幾個經常使用的。

  • RequireHttps,強制使用HTTPS協議訪問。它將瀏覽器的請求重定向到相同的controller和action,並加上 https:// 前綴。
  • OutputCache,將action方法的輸出內容進行緩存。
  • AsyncTimeout/NoAsyncTimeout,用於異步Controller的超時設置。(異步Controller的內容請訪問 xxxxxxxxxxxxxxxxxxxxxxxxxxx)
  • ChildActionOnlyAttribute,使用action方法僅能被Html.Action和Html.RenderAction方法訪問。

這裏咱們選擇 OutputCache 這個Filter來作個示例。新建一個 SelectiveCache controller,代碼以下:

public class SelectiveCacheController : Controller {
    public ActionResult Index() { 
        Response.Write("Action method is running: " + DateTime.Now); 
        return View(); 
    } 

    [OutputCache(Duration = 30)] 
    public ActionResult ChildAction() { 
        Response.Write("Child action method is running: " + DateTime.Now); 
        return View(); 
    } 
}

這裏的 ChildAction 應用了 OutputCache filter,這個action將在view內被調用,它的父action是Index。

如今咱們分別建立兩個View,一個是ChildAction.cshtml,代碼以下:

@{ 
    Layout = null; 
} 
 
<h4>This is the child action view</h4>

另外一個是它的Index.cshtml,代碼以下:

@{ 
    ViewBag.Title = "Index"; 
} 
 
<h2>This is the main action view</h2> 
 
@Html.Action("ChildAction")

運行程序,將URL定位到  /SelectiveCache ,過幾秒刷新一下,可看到以下結果:

 

 


參考:《Pro ASP.NET MVC 4 4th Edition》

相關文章
相關標籤/搜索