C# ASP.NET Core

ASP.NET Core 中的 Filter

王煜翔 2020/12/16 00:22:56
94

因為在最近的案子裡使用到了不少 Filter 對 API 進行檢查,所以想在這次的文章中介紹關於 ASP.NET Core 中的 Filter。

 

ASP.NET Core中的 Filter(篩選條件)

Filter可以在進入 Action 與離開 Action 時對 Requset 進行檢查或加工。而將部分檢查放在 Filter 中,

除了可以將部分重複的邏輯抽離出來,也可以透過不同種類 Filter 來增加檢查的精確性。

在ASP.NET Core中,Request在經過所有 Middleware後,才會執行Filter的內容,如下圖所示:

Request 在進入與離開 Action 時都會經過 Filter ,而 Filter 又分成五種不同的類型,

分別為 Authorization filters、Resource filters、Action filters、Exception filters 與 Result filters。

這五種 Filter 有不同的執行時機與執行順序,從 Request 進入到回傳 Response的流程如下圖所示:

從流程圖可以看到不同類型的 Filter 進入的順序與時機都不盡相同,可根據自己的需求決定要使用什麼樣的 Filter 最能達到所需要的效果。

接下來將簡單介紹五種Filter的特性與撰寫方法。

 

Authorization Filters

Authorization Filter 是所有 Filters 最優先執行的Filter,主要用於檢查使用者是否有授權,若無授權在此階段就可先擋下,擋下後將不再執行之後的流程。

實作方法:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;


namespace FilterTest.Infars.Filters
{
    public class AuthorizationFilter : IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            //可在此進行權限檢查
            context.HttpContext.Response.WriteAsync($"進入Authorization Filter。 \r\n");
        }
    }
}

 

Resource filters

Resource filters 會在執行完 Authorization Filter 後,Model Binding 前執行,以及 Pipeline 中其餘部分都完成後執行。

跟 Action Filter 一樣是正常流程下必定會經過的 Filter,只是進入時機不同。

實作方法:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;

namespace FilterTest.Infars.Filters
{
    public class ResourceFilter : IResourceFilter
    {
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            //Model Binding前執行
            context.HttpContext.Response.WriteAsync($"進入 Resource Filter。 \r\n");
        }

        public void OnResourceExecuted(ResourceExecutedContext context)
        {
            // Pipeline 其他動作完成,要回傳Response前執行
            context.HttpContext.Response.WriteAsync($"離開 Resource Filter \r\n");
        }
    }
}

 

Action Filters

Action Filters 算是最簡單好用的 Filter,若沒有在前兩個 Filter 就先被擋下的話,基本上進入與離開Action 都會經過 Action Filter。

跟 Resource Filters 的差異主要在於進出時機的不同,可以在執行Action前或Action執行後進行檢查。

實作方法:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;


namespace FilterTest.Infars.Filters
{
    public class ActionFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context)
        {
            //Action 執行前執行
            context.HttpContext.Response.WriteAsync($"進入Action Filter。 \r\n");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            //Action 執行後執行
            context.HttpContext.Response.WriteAsync($"離開 Action Filter。 \r\n");
        }
    }
}

 

Exception filters 

發生 Exception 後會進入的 Filter。

實作方法:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;

namespace FilterTest.Infars.Filters
{
    public class ExceptionFilter : IExceptionFilter
    {
        public void OnException(ExceptionContext context)
        {
            //發生Exception時執行
            context.HttpContext.Response.WriteAsync($"進入 Exception Filter \r\n");
        }
    }
}

 

Result filters

Result Filter會在 Action 執行完畢後執行,若是在 Action 中發生 Exception 則不會經過這個Filter。

實作方法:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;


namespace FilterTest.Infars.Filters
{
    public class ResultFilter : IResultFilter
    {
        public void OnResultExecuting(ResultExecutingContext context)
        {
            //Result 執行前執行
            context.HttpContext.Response.WriteAsync($"進入 Result Filter。 \r\n");
        }

        public void OnResultExecuted(ResultExecutedContext context)
        {
            //Result 執行後執行
            context.HttpContext.Response.WriteAsync($"離開 Result Filter。 \r\n");
        }
    }
}

 

介紹完了這五種Fliter的特性與實作方式後,接下來要介紹在 ASP.NET Core 中如何註冊這些 Filters。

在ASP.NET Core中,可以透過全域註冊或區域註冊的方式來註冊 Filters,註冊方式如下。

 

全域註冊

在 Startup.cs 中,於 ConfigureServices 裡的 MVC 服務中註冊 Filter。

註冊方式如下:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(options =>
            {
                options.Filters.Add(typeof(AuthorizationFilter));
                options.Filters.Add(typeof(ResourceFilter));
            });
            .
            .
            .
        }

 

透過全域註冊的方式所註冊的Filters將會被套用到所有的Request,若 Filter 中有透過DI容器注入服務的話,透過全域註冊的方式其相依性會由DI容器滿足。

若只是想在特定的 Request 上使用我們自訂的Filter的話,則需透過區域註冊的方式進行註冊。

 

區域註冊

區域註冊可以透過 TypeFilterAttribute 進行註冊。在 Controller 或 Action 上加上 [TypeFilter(typeof(YourFilter)] 的方式進行區域註冊。

註冊方式如下:

        [TypeFilter(typeof(ActionFilter))]
        [HttpGet("test")]
        public async Task<IActionResult> Test()
        {
            return Ok();
        }

 

透過 TypeFilterAttribute 參考的類型不需要向DI容器註冊,但所參考的類型中的相依性會由DI容器滿足,若是在 Filter 中透過DI容器注入服務,

用此註冊方式可以正常的執行。

 

除了透過 TypeFilterAttribute 進行區域註冊外,Filter 也可以透過繼承 Attribute的方式讓自己可以作為附加屬性執行。

繼承方式如下:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using System;

namespace FilterTest.Infars.Filters
{
    public class ActionFilter : Attribute, IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context)
        {
            //Action 執行前執行
            context.HttpContext.Response.WriteAsync($"進入Action Filter。 \r\n");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            //Action 執行後執行
            context.HttpContext.Response.WriteAsync($"離開 Action Filter。 \r\n");
        }
    }
}

 

繼承 Attribute 後,Filter便可直接以屬性的方式加到 Controller 或 Action 上來使用,使用方式如下:

        [ActionFilter]
        [HttpGet("test")]
        public async Task<IActionResult> Test()
        {
            return Ok();
        }

 

但須注意的是,透過繼承 Attribute的方式來使用的 Filter中的相依性不會透過 DI 容器滿足,故若在 Filter 中有透過 DI 容器注入服務的話,

還是要透過另外兩種註冊方式才能正常運行。

 

實際執行結果

在這次的實際執行中,我們將 Authorization Filter、 Resource Filter 與 Result Filter 進行全域註冊,Action Filter 與 Exception Filter

則分別採用 Attribute 與 TypeFilter 的方式進行區域註冊。

在這次的測試中,我們也在 Authorization Filter 中注入一個簡單的 Service,此 Service 的相依性將會透過 DI 容器滿足。

首先,我們先測試一個正常的 Request 傳入到回傳 Response 會產生什麼結果

        [ActionFilter]
        [HttpGet("test")]
        public async Task<IActionResult> Test()
        {
            return Ok();
        }

在這個測試中,Authorization Filter、 Resource Filter 與 Result Filter 使用全域註冊,Action Filter使用區域註冊。

 

從執行結果中可以看到不同 Filter 的進出時間點的LOG,並可在第二行中看到 DI 容器成功的滿足了 Service的相依性。

也可以從正常的執行狀況中發現,在沒有回傳 Exception 的狀態下,是不會 Exception Filter的。

 

接下來將測試若有 Exception發生時,Filter 的執行狀況。

        [TypeFilter(typeof(ExceptionFilter))]
        [HttpGet("ErrorTest")]
        public async Task<IActionResult> ErrorTest()
        {
            throw new NotImplementedException();
        }

在這個測試中,Authorization Filter、 Resource Filter 與 Result Filter 使用全域註冊,Exception Filter使用區域註冊。

從執行的結果可以看到,發生 Exception 後,會進入 Exception Filter,而發生Exception後就不會再進入Result Filter。

從本次的實際執行結果與 Filter 的流程圖中,可以看到不同的 Filter 有其觸發的順序,所以我們在在使用Filter時可以根據進入 Filter的時機與使用狀態決定

要使用什麼樣的Filter。

 

結語

就像在前言所提到的,本次文章是因為在最近在開發專案時用到了一些 Filter。以這次的需求來說,因客戶的使用者權限設定較為複雜,無法單純的透過

[Authorize] 屬性對 Controller 與 Action 進行權限的控管,所以在本次的開發中,我們透過 Authorization Filter 來進行更加精細的權限檢查,將這些

檢查寫在自定義的 Authorization Filter 中,也可以確保未授權的用戶在使用到未授權的API時,不會實際觸碰到Action的層面,也透過一些 Action Filter

來檢查與紀錄Request的狀態。

整體而言,Filter 是簡單且容易使用的,在開發時可根據目前著手的案件需求,選擇適合自己的 Filter 進行開發。

那麼這次的 Filter 介紹就到此告一段落,我們下次再見。

王煜翔