D-15 過濾器 ? filter ? attribute

filter

眼尖的小光在昨日的內容中看到了一個有趣的東西,就是MiddlewareFilter,所以這個filter又困擾了他一整晚,所以一大早他就來了解甚麼是filter。

過濾器是甚麼

「前輩阿,昨天遇到的一個MiddlewareFilter這是甚麼東西啊?」
一大早小光就來問大頭關於過濾器的東西,因為這問題困擾了他一整晚所以他的黑眼圈都跑出來了。
「哈哈哈,你的眼睛真利阿,沒想到這樣的東西都會被你注意到,那我們今天就來說明一下甚麼是過濾器。」
剛說完這句話的大頭似乎想到甚麼停頓了一下,不過過了幾秒後他就這麼說了。
「不過在說明過濾器之前有個東西要先介紹,那就是屬性。」

屬性 attribute

在說明過濾器之前要先說明屬性,所以請大家先看一下這個屬性,這邊的屬性是attribute跟前面講的property中文翻譯是一樣,但是內容差很多,所以本篇所說的屬性都是指attribute,這邊提到的屬性功用很多,但是一樣的地方都是以[]包覆起來的。然而在本篇用到的屬性的功用是掛上該過濾器的屬性就可以在執行該Action或是該Controller的Action都會進入過濾器之中,就視該過濾器是掛在Controller還是哪個Action,所以接下來開始說明過濾器。

過濾器 filter

提到過濾器還是會提到請求的流水線,但目前要說的是通過中介層後面那一段,所以大家先看一下過濾器

甚麼是過濾器

所謂的過濾器就是在請求的流水線進入Action之前跟之後會做些處理的動作,所以大家先看一下下圖。

過濾器的流水線 過濾器的流水線

所以說當路由中到特定的Action之後要準備執行之前會先檢查該Action有沒有掛過濾器的的屬性,當有掛特定過濾器的屬性時會先執行該過濾器,並且在結束時會執行該過濾器。

過濾器種類

雖然說都是過濾器,但是過濾器也有許多種類,請參考下列表格。

種類 內容
授權 優先執行,用來判斷使用者是否已針對要求取得授權。 如果要求未獲授權,會進行較短的線路。
資源 會在授權之後執行。在模型系結之前執行程式碼。並在管線的其餘部分完成之後執行程式碼。
動作 在呼叫動作方法之前和之後立即執行程式碼,可以變更傳遞至動作的引數。並且可以變更動作傳回的結果。不支援Razor頁面。
例外狀況 會將全域原則套用至回應主體寫入之前發生的未處理例外狀況。
結果 會在執行動作結果之前和之後立即執行程式碼。 它們只有在動作方法執行成功時才執行。 它們適用於必須包圍檢視或格式器執行的邏輯。

而過濾器他們之間執行的先後順序如下圖所示。

過濾器類別間的流水線 過濾器類別間的流水線

實做過濾器

我們這邊以動作過濾器為例子來說明如何實作一個過濾器,所以請大家先看下列內容

public class CustomActionFilter : IActionFilter 
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // 執行動作前的處理
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // 執行動作後的處理
    }
}

所以過濾器的實作就是這麼簡單,想好你需要做些甚麼在對的時間點實作即可,不過這邊有分兩種介面,除了上述的IActionFilter之外,還有IAsyncActionFilter,這邊有個注意事項,請實作同步非同步版本的篩選條件介面,而不要同時實作這兩者。執行階段會先檢查以查看篩選條件是否會實作非同步介面,如果是,便會呼叫該介面。 如果沒有,它會呼叫同步介面的方法。如果同時在單一類別中實作非同步和同步介面,系統只會呼叫非同步方法。接下來在看看要如何把這過濾器掛上去。

使用過濾器

依據過濾器的範圍有分為以下兩種,

  • 全域
  • 區域

關於全域的過濾器可以用以下方式使用。

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
    {
        options.Filters.Add(typeof(CustomActionFilter),
                            int.MinValue);
    });
}

而區域的過濾器有三個掛上過濾器的方法,如下列所示。

  • ServiceFilterAttribute
  • TypeFilterAttribute
  • IFilterFactory。

首先說明如何使用ServiceFilterTypeFilter方式掛上過濾器。

// 用ServiceFilter在Action掛上過濾器
[ServiceFilter(typeof(CustomActionFilter))]
public IActionResult Index()
{
    return View();
}

// 用TypeFilter在Action掛上過濾器
[TypeFilter(typeof(CustomActionFilter),
    Arguments = new object[] { "這是參數" })]
public IActionResult Hi(string name)
{
    return Content($"Hi {name}");
}

以上兩個使用的方式差別如下,使用TypeFilter參考的類型不需要向DI容器註冊。不過它們的相依性會由DI容器滿足。而且TypeFilter可以選擇性地接受類型的建構函式引數。而ServiceFilter需要先在DI註冊過濾器。

最後用IFilterFactory掛上過濾器的方式如下。

// 工廠的實作
public class CustomActionFactoryAttribute : Attribute, IFilterFactory
{
    // Implement IFilterFactory
    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        return new CustomActionFilter();
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

// 掛工廠在方法上
[CustomActionFactoryAttribute]
public IActionResult CustomActionFactory()
{
    // ...
}

這邊要注意IFilterFactory.IsReusable當過濾器的來源是明確的、為無狀態,而且可以在多個HTTP要求中安全地使用時,才設定為傳回true

後記

「前輩阿,這幾天講的很多東西都在路由、中介層跟過濾器做完了,那Action要做甚麼阿。」
聽完了這幾天的內容後小光這麼問大頭。
「哈哈哈,你太小看網頁程式了,你這問題明天就會告訴你Controller跟Action要做甚麼動作,其中一項就是畫面渲染。」
所以大家就期待一下明天的razor渲染了。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *