ASP.NET Core 中的 Filter
因為在最近的案子裡使用到了不少 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 介紹就到此告一段落,我們下次再見。