ASP.NET MVC5基礎-過濾器(Filters)詳解


什么是過濾器?

通過上一篇關於Controller控制器的文章我們知道,MVC中的每一個請求,都會分配給相應的控制器(Controller)和對應的行為方法(Action)去處理,那么如果我們想要在Action處理的前后加上一些額外的處理邏輯怎么辦呢?這時候就用到了過濾器(Filters)。

在ASP.NET MVC的請求處理過程中有19個管道事件,這些事件分布在請求處理的各個節點中,比如BeginRequest(開始處理請求時觸發)、AuthenticateRequest(對請求進行身份驗證時觸發)、AuthorizeRequest(對請求進程授權時觸發)…等等等等。而過濾器的主要作用就是將我們的附加邏輯注入到這些請求處理管道中。

在實際業務中,在Action方法前后添加額外附加邏輯的情況有很多,過濾器就是用來完成此功能。通過過濾器可以將與業務邏輯無關但經常需要執行的代碼分離開,使我們的代碼邏輯性更加清晰,代碼更加簡潔。

過濾器的類型與作用

MVC給我們提供了四種過濾器,基本滿足了我們實際業務中常用的需求,包括以下:

過濾器類型名稱

實現的接口

默認的實現類

作用

執行的順序與節點

授權過濾器

IAuthorizationFilter

AuthorizeAttribute

用於限制進入控制器或控制器的某個行為方法

在控制器方法調用前執行,所有過濾器中最先執行的

動作過濾器

IActionFilter

ActionFilterAttribute

用於進入動作方法之前或之后的處理

在控制器方法調用前/后執行

結果過濾器

IResultFilter

ActionFilterAttribute

用於動作方法返回結果之前或之后的處理

在控制器方法調用完,跳轉至view頁面前/后執行

異常處理過濾器

IExceptionFilter

HandleErrorAttribute

用於處理某個動作方法或某個控制器里面拋出的異常

在控制器方法拋出異常時執行

這四種類型的接口是MVC對過濾器的一個接口規范,同時MVC默認通過AuthorizeAttribute(授權)、HandleErrorAttribute(異常處理)、ActionFilterAttribute(動作和結果)三個類實現了這四個接口。

需要注意的是ActionFilterAttribute類既實現了IActionFilter接口,也實現了IResultFilter接口。這是個抽象類,要求必須提供一個實現,AuthorizeAttribute和HandleErrorAttribute類則包含了一些有用的特性,可以不必創建派生類進行使用。所以我們一般都會通過繼承ActionFilterAttribute類,實現自定義的過濾器。

除以上接口之外,我們還要用到FilterAttribute類,這個類將我們的過濾器包裝成了特性,使我們的過濾器可以方便的在Action方法上方使用。

定義過濾器

過濾器有以下幾個特點:

  • 可用於動作方法(Action)

  • 可用於控制器(Controller)

  • 可多個Filter同時使用

  • 不同級別可以混搭

  • 可運用於基類的過濾器,會影響該基類的所有派生類

下面我們逐一介紹下基本過濾器的使用方法。

授權過濾器

所有實現了IAuthorizationFilter接口的都可以稱之為授權過濾器。它的接口定義如下:

namespace System.Web.Mvc
{
    //
    // 摘要:
    //     定義授權篩選器所需的方法。
    public interface IAuthorizationFilter
    {
        //
        // 摘要:
        //     在需要授權時調用。
        //
        // 參數:
        //   filterContext:
        //     篩選器上下文。
        void OnAuthorization(AuthorizationContext filterContext);
    }
}

授權過濾器是最先運行的過濾器,它運行在其它過濾器和Action方法之前。客戶端請求在調用Action之前,MVC框架會檢測Action上是否有授權過濾器,如果有會調用OnAuthorization方法,如果此方法批准了請求,才會調用相應的Action。流程如圖:

MVC默認使用AuthorizeAttribute實現了IAuthorizationFilter接口,所以我們可以在Action方法上直接添加Authorize特性標簽來驗證授權:

打開Index頁面,會顯示無權限:

由於使用的是MVC自帶的授權驗證方法,未能符合它的驗證機制,所以無權限查看。通常我們需要添加一個新的派生自AuthorizeAttribute類的授權過濾器來完成我們自己業務邏輯。

下面我們自定義一個授權過濾器。我們在MVC項目中添加一個Filters文件夾,我們所有自定義的過濾器都可以放到這個文件夾下,便於管理。

在Filters下創建一個類,類名為MyAuthorizeAttribute。需要注意,過濾器要以Attribute結尾,這是MVC的約定。代碼如下:

public class MyAuthorizeAttribute : AuthorizeAttribute
{
    //重寫授權檢查方法,返回值為true,允許訪問,false,禁止訪問。
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        //請求參數user為空,禁止訪問
        if (string.IsNullOrEmpty(HttpContext.Current.Request.QueryString["user"]))
        {
            return false;
        }
        return true;
    }
}

可以看到,我們只要重寫AuthorizeCore方法就可以根據我們的業務需求判斷是否有權限訪問,返回值為true允許訪問,返回值為false禁止訪問。

回到HomeController,我們給About方法加上我們自定義的特性:

我們看看效果:

可以看到,當About頁面沒有user參數時,會提示無權限,有user參數則可以訪問通過。

在實際業務中我們可以使用授權過濾器來管理用戶登錄狀態的授權驗證。當然,我的這個例子只是基礎的用法,實際業務比這復雜的多,那么就需要我們自己去思考設計授權過濾器方法了。

動作過濾器

動作過濾器需要實現IActionFilter接口,接口定義如下:

//
// 摘要:
//     定義操作篩選器中使用的方法。
public interface IActionFilter
{
    //
        // 摘要:
        //     在執行操作方法后調用。
        //
        // 參數:
        //   filterContext:
        //     篩選器上下文。
        void OnActionExecuted(ActionExecutedContext filterContext);
    //
        // 摘要:
        //     在執行操作方法之前調用。
        //
        // 參數:
        //   filterContext:
        //     篩選器上下文。
        void OnActionExecuting(ActionExecutingContext filterContext);
}

我們看到該接口里有兩個方法OnActionExecuting和OnActionExecuted,前者在動作方法執行前調用,后者在動作方法執行后調用。

OnActionExecuting方法是在Action方法執行前調用的,那么我們可以利用這個方法來檢測請求,並且可以在這里修改請求,取消請求等等操作。

OnActionExecuting方法的參數是一個ActionExecutingContext對象,它繼承自ControllerContext類,屬性如下:

名稱

類型

說明

ActionDescriptor

ActionDescriptor

獲取或設置操作描述符。

ActionParameters

IDictionary<string, object>

獲取或設置操作方法參數。

Result

ActionResult

獲取或設置由操作方法返回的結果。

我們添加一個自定義的Action過濾器。由於ActionFilterAttribute類實現了IActionFilter接口,所以我們直接繼承ActionFilterAttribute類即可,並且重寫OnActionExecuting和OnActionExecuted方法。如下:

public class MyActionAttribute : ActionFilterAttribute
{
    /// <summary>
    /// Action調用之前運行
    /// </summary>
    /// <param name="filterContext"></param>
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (string.Equals(filterContext.HttpContext.Request.HttpMethod, "get", StringComparison.CurrentCultureIgnoreCase))
        {
            filterContext.Result = new HttpNotFoundResult("只允許POST請求!");
        }
    }
    /// <summary>
    /// Action調用之后運行
    /// </summary>
    /// <param name="filterContext"></param>
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {

    }
}

我們給Index方法添加上MyAction特性標簽:

打開Index頁,顯示如下:

可以看到,頁面返回了404錯誤,提示信息為我們設置的Message。

OnActionExecuted方法在Action操作方法調用之后執行,傳遞給OnActionExecuted方法的參數是ActionExecutedContext對象。這個類比ActionExecutingContext對象多了些屬性,如下:

名稱

類型

說明

ActionDescriptor

ActionDescriptor

獲取或設置操作描述符。

Canceled

bool

獲取或設置一個值,該值指示此ActionExecutedContext 對象已被取消。

Exception

Exception

獲取或設置在操作方法的執行過程中發生的異常(如果有)。

ExceptionHandled

bool

獲取或設置一個值,該值指示是否處理異常。

Result

ActionResult

獲取或設置由操作方法返回的結果。

我們可以通過OnActionExecuted方法來執行一些跨越動作方法的任務,比如我們可以用它來獲取動作方法執行的時間。我們修改 MyActionAttribute 過濾器代碼如下:

public class MyActionAttribute : ActionFilterAttribute
{
    private Stopwatch timer;

    /// <summary>
    /// Action調用之前運行
    /// </summary>
    /// <param name="filterContext"></param>
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        timer = Stopwatch.StartNew();
    }
    /// <summary>
    /// Action調用之后運行
    /// </summary>
    /// <param name="filterContext"></param>
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        timer.Stop();
        filterContext.HttpContext.Response.Write($"<div>方法執行時間:{timer.Elapsed.TotalSeconds:F6}s</div>");
    }
}

我們在方法啟動之前啟動了一個計時器,在方法執行后停止了它,並且將這個時間間隔輸出到我們的頁面上。重新編譯打開Index頁面,顯示如下:

結果過濾器

結果過濾器,顧名思義針對的是動作方法返回的結果,它在我們的動作方法結果返回前后執行。

創建結果過濾器需要實現IResultFilter接口。ActionFilterAttribute類幫我們實現了IResultFilter接口,我們可以直接繼承ActionFilterAttribute創建我們的過濾器,然后通過重寫OnResultExecutin和OnResultExecuting(在執行操作結果后調用)方法來實現過濾器規則。

OnResultExecuting方法會在執行操作結果前調用,這個方法的參數是一個ResultExecutingContext對象,屬性如下:

名稱

類型

說明

Cancel

bool

獲取或設置一個值,該值指示此 ResultExecutingContext 值是否為“cancel”。

Result

ActionResult

獲取或設置操作結果。

OnResultExecuted方法在執行操作結果后調用,這個方法的參數是一個ResultExecutingContext對象,屬性如下:

名稱

類型

說明

Canceled

bool

獲取或設置一個值,該值指示此 ResultExecutingContext 值是否為“cancel”。

Exception

Exception

獲取或設置在操作方法的執行過程中發生的異常(如果有)。

ExceptionHandled

bool

獲取或設置一個值,該值指示是否處理異常。

Result

ActionResult

獲取或設置操作結果。

我們可以使用這兩個方法在Action方法返回結果前后進行操作,具體操作的代碼我就不贅述了。

異常處理過濾器

異常處理過濾器需要實現的接口為IExceptionFilter。我們看下接口的定義:

//
// 摘要:
//     定義異常篩選器所需的方法。
public interface IExceptionFilter
{
    //
    // 摘要:
    //     在發生異常時調用。
    //
    // 參數:
    //   filterContext:
    //     篩選器上下文。
    void OnException(ExceptionContext filterContext);
}

接口方法OnException可以看到它在我們的方法中出現異常時觸發,MVC默認用HandleErrorAttribute類來實現了此接口,我們自己定義的異常過濾器可以繼承此類進行擴展。

OnException方法中傳遞的參數是一個ExceptionContext對象,它的屬性如下:

名稱

類型

說明

Exception

Exception

獲取或設置異常對象。

ExceptionHandled

bool

獲取或設置一個值,該值指示是否已處理異常。

Result

ActionResult

獲取或設置操作結果。

我們來定義一個異常過濾器,代碼如下:

public class MyExceptionAttribute : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        //如果有異常,跳轉到異常頁面。
        if (filterContext.Exception != null)
        {
            //跳轉到自定義的錯誤頁
            ActionResult view = new ViewResult() { ViewName = "Error" };
            filterContext.Result = view;
            //異常處理結束后,一定要將ExceptionHandled設置為true,否則仍然會繼續拋出錯誤。
            filterContext.ExceptionHandled = true;
        }
    }
}

當我們的方法中出現異常時,會將Views文件夾下Shared中的Error.cshtml頁面返回到客戶端,客戶端頁面不再顯示成黃頁,給用戶一個良好的體驗。

我們給Index方法添加上我們的異常處理器,再加一段引發異常的代碼看下效果。

打開Index視圖:

視圖中顯示的是Error頁面中的內容。

通常我們用異常處理器來記錄我們的程序異常日志,或者在產生異常時給客戶端返回一個友好的提示內容。

過濾器的使用方法

在上文的一些例子中,我們把過濾器的特性都定義在了Action方法上,其實過濾器不僅可以應用在Action方法中,還可在應用在Controller和全局配置中。

應用在Controller中的使用方法和Action一直,在Controller類名上方添加特性標簽即可。如圖:

全局環境下的過濾器,則需要注冊到FilterConfig文件中,例如MVC默認給我們注冊的HandleErrorAttribute異常處理器:

並且我們可以注冊很多個過濾器在全局環境下,那么在此注冊的過濾器會應用到整個應用程序當中。

總結

本章對過濾器的類型,作用,定義以及使用方法做了一些說明,當然這些都是比較基礎的內容,真正深入的理解還得多多使用,如果文章中有錯誤或者不足的地方,請大家在評論中指正出來。

更多內容可訪問我的博客:http://www.yunc.top/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM