天天看點

【.Net Core】過濾器Filter

文章目錄

    • 使用背景
    • 工作原理
      • 篩選器類型
    • 實作
      • 多個篩選器階段
      • 内置篩選器屬性
    • 篩選器作用域和執行順序
      • 預設執行順序
      • 控制器級别篩選器
      • 替代預設順序
    • 取消和設定短路
    • 依賴關系注入
      • ServiceFilterAttribute
      • TypeFilterAttribute
    • 授權篩選器
    • 資源篩選器
    • 操作篩選器
    • 異常篩選器
    • 結果篩選器
      • IResultFilter 和 IAsyncResultFilter
      • IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter
    • IFilterFactory
      • 在屬性上實作 IFilterFactory
    • 在篩選器管道中使用中間件
    • 線程安全
    • 對比中間件
    • 來源

使用背景

過濾器有什麼作用,在什麼場景下适合用到它?

假設一個項目進展到快結束的時候,項目leader為了保證程式的穩定性和可監控和維護性要求将所有的方法加上日志,如果項目比較龐大,方法非常多,那豈不是得費很大得勁來完成這樣一件事情。不過不用擔心,咋們遇到的問題,偉大的語言設計者早已幫我們想好了解決辦法過濾器,過濾器是一種AOP(面向切面程式設計)技術的展現,AOP具有松耦合,易擴充,代碼可複用的特點。

通常我們在這些場景下如 身份驗證 、 日志記錄 、異常擷取 等會使用到過濾器

工作原理

篩選器在 ASP.NET Core 操作調用管道(有時稱為篩選器管道)内運作。 篩選器管道在 ASP.NET Core 選擇了要執行的操作之後運作:

【.Net Core】過濾器Filter

篩選器類型

每種篩選器類型都在篩選器管道中的不同階段執行:

授權篩選器:

  • 首先運作。
  • 确定使用者是否獲得請求授權。
  • 如果請求未獲授權,可以讓管道短路。

資源篩選器:

  • 授權後運作。
  • OnResourceExecuting 在篩選器管道的其餘階段之前運作代碼。 例如,OnResourceExecuting 在模型綁定之前運作代碼。
  • OnResourceExecuted 在管道的其餘階段完成之後運作代碼。

操作篩選器:

  • 在調用操作方法之前和之後立即運作。
  • 可以更改傳遞到操作中的參數。
  • 可以更改從操作傳回的結果。
  • 不可在 Razor Pages 中使用。

終結點篩選器:

  • 在調用操作方法之前和之後立即運作。
  • 可以更改傳遞到操作中的參數。
  • 可以更改從操作傳回的結果。
  • 不可在 Razor Pages 中使用。
  • 可以在操作和基于路由處理程式的終結點上調用。
  • 異常篩選器在向響應正文寫入任何内容之前,對未經處理的異常應用全局政策。

結果篩選器:

  • 在執行操作結果之前和之後立即運作。
  • 僅當操作方法成功執行時才會運作。
  • 對于必須圍繞視圖或格式化程式的執行的邏輯,會很有用。

下圖展示了篩選器類型在篩選器管道中的互動方式:

【.Net Core】過濾器Filter

Razor 頁面還支援 Razor 頁面篩選器,這些篩選器在頁面處理程式之前和之後 Razor 運作。

實作

篩選器通過不同的接口定義支援同步和異步實作。

同步篩選器在其管道階段之前和之後運作。 例如,OnActionExecuting 在調用操作方法之前調用。 OnActionExecuted 在操作方法傳回之後調用:

public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}
           

異步篩選器定義 On-Stage-ExecutionAsync 方法。 例如,OnActionExecutionAsync:

public class SampleAsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // Do something before the action executes.
        await next();
        // Do something after the action executes.
    }
}
           

在前面的代碼中,SampleAsyncActionFilter 具有執行操作方法的 ActionExecutionDelegate (next)。

多個篩選器階段

可以在單個類中實作多個篩選器階段的接口。 例如,ActionFilterAttribute 類可實作:

  • 同步:IActionFilter 和 IResultFilter
  • 異步:IAsyncActionFilter 和 IAsyncResultFilter
  • IOrderedFilter

篩選器接口的同步和異步版本任意實作一個,而不是同時實作 。 運作時會先檢視篩選器是否實作了異步接口,如果是,則調用該接口。 如果不是,則調用同步接口的方法。 如果在一個類中同時實作異步和同步接口,則僅調用異步方法。 使用抽象類(如 ActionFilterAttribute)時,将為每種篩選器類型僅重寫同步方法或僅重寫異步方法。

内置篩選器屬性

ASP.NET Core 包含許多可子類化和自定義的基于屬性的内置篩選器。 例如,以下結果篩選器會向響應添加标頭:

public class ResponseHeaderAttribute : ActionFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public ResponseHeaderAttribute(string name, string value) =>
        (_name, _value) = (name, value);

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(_name, _value);

        base.OnResultExecuting(context);
    }
}
           

通過使用屬性,篩選器可接收參數,如前面的示例所示。 将 ResponseHeaderAttribute 添加到控制器或操作方法,并指定 HTTP 标頭的名稱和值:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...
           

使用浏覽器開發人員工具等工具來檢查标頭。 在響應标頭下,将顯示 filter-header: Filter Value。

以下代碼将 ResponseHeaderAttribute 應用于控制器和操作:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...

    [ResponseHeader("Another-Filter-Header", "Another Filter Value")]
    public IActionResult Multiple() =>
        Content("Examine the response headers using the F12 developer tools.");
}
           

Multiple 操作的響應包括以下标頭:

  • filter-header: Filter Value
  • another-filter-header: Another Filter Value

多種篩選器接口具有相應屬性,這些屬性可用作自定義實作的基類。

篩選器屬性:

  • ActionFilterAttribute
  • ExceptionFilterAttribute
  • ResultFilterAttribute
  • FormatFilterAttribute
  • ServiceFilterAttribute
  • TypeFilterAttribute

無法将篩選器應用于 Razor 頁面處理程式方法。 它們可以應用于 Razor 頁面模型或全局應用。

篩選器作用域和執行順序

可以将篩選器添加到管道中的三個作用域之一:

  • 在控制器或 Razor 頁面上使用屬性。
  • 在控制器操作上使用屬性。 無法将篩選器屬性應用于 Razor 頁面處理程式方法。
  • 所有控制器、操作和 Razor 頁面的全局篩選器,如下面的代碼所示:
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<GlobalSampleActionFilter>();
});
           

預設執行順序

當管道的某個特定階段有多個篩選器時,作用域可确定篩選器執行的預設順序。 全局篩選器涵蓋類篩選器,類篩選器又涵蓋方法篩選器。

在篩選器嵌套模式下,篩選器的 after 代碼會按照與 before 代碼相反的順序運作。 篩選器序列:

  • 全局篩選器的 before 代碼。
    • 控制器篩選器的 before 代碼。
    • 操作方法篩選器的 before 代碼。
    • 操作方法篩選器的 after 代碼。

      控制器篩選器的 after 代碼。

  • 全局篩選器的 after 代碼。

下面的示例闡釋了為同步操作篩選器運作篩選器方法的順序:

序列 篩選器作用域 篩選器方法
1 全球 OnActionExecuting
2 控制器 OnActionExecuting
3 操作 OnActionExecuting
4 操作 OnActionExecuted
5 控制器 OnActionExecuted
6 全球 OnActionExecuted

控制器級别篩選器

繼承自 Controller 的每個控制器都包括 OnActionExecuting、OnActionExecutionAsync 和 OnActionExecuted 方法。 這些方法可覆寫為給定操作運作的篩選器:

  • OnActionExecuting 在所有操作篩選器之前運作。
  • OnActionExecuted 在所有操作篩選器之後運作。
  • OnActionExecutionAsync 在所有操作篩選器之前運作。 調用 next 後的代碼在操作篩選器之後運作。

以下 ControllerFiltersController 類:

  • 将 SampleActionFilterAttribute ([SampleActionFilter]) 應用于控制器。
  • 重寫 OnActionExecuting 和 OnActionExecuted。
[SampleActionFilter]
public class ControllerFiltersController : Controller
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuting)}");

        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuted)}");

        base.OnActionExecuted(context);
    }

    public IActionResult Index()
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(Index)}");

        return Content("Check the Console.");
    }
}
           

導航到 https://localhost:/ControllerFilters 運作以下代碼:

  • ControllerFiltersController.OnActionExecuting
    • GlobalSampleActionFilter.OnActionExecuting
    • SampleActionFilterAttribute.OnActionExecuting
    • ControllerFiltersController.Index
    • SampleActionFilterAttribute.OnActionExecuted
    • GlobalSampleActionFilter.OnActionExecuted
  • ControllerFiltersController.OnActionExecuted

控制器級别篩選器将 Order 屬性設定為 int.MinValue。 控制器級别篩選器無法設定為在将篩選器應用于方法之後運作。 在下一節對 Order 進行了介紹。

替代預設順序

可以通過實作 IOrderedFilter 來重寫預設執行序列。 IOrderedFilter 公開了 Order 屬性來确定執行順序,該屬性優先于作用域。 具有較低的 Order 值的篩選器:

  • 在具有較高的 Order 值的篩選器之前運作 before 代碼。
  • 在具有較高的 Order 值的篩選器之後運作 after 代碼。

在控制器級别篩選器示例中,GlobalSampleActionFilter 具有全局作用域,是以它在具有控制器作用域的 SampleActionFilterAttribute 之前運作。 若要首先運作 SampleActionFilterAttribute,請将其順序設定為 int.MinValue:

[SampleActionFilter(Order = int.MinValue)]
public class ControllerFiltersController : Controller
{
    // ...
}
           

若要首先運作全局篩選器 GlobalSampleActionFilter,請将其 Order 設定為 int.MinValue:

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<GlobalSampleActionFilter>(int.MinValue);
});
           

取消和設定短路

通過設定提供給篩選器方法的 ResourceExecutingContext 參數上的 Result 屬性,可以使篩選器管道短路。 例如,以下資源篩選器将阻止執行管道的其餘階段:

public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.Result = new ContentResult
        {
            Content = nameof(ShortCircuitingResourceFilterAttribute)
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context) { }
}
           

在下面的代碼中,[ShortCircuitingResourceFilter] 和 [ResponseHeader] 篩選器都以 Index 操作方法為目标。 ShortCircuitingResourceFilterAttribute 篩選器:

  • 先運作,因為它是資源篩選器且 ResponseHeaderAttribute 是操作篩選器。
  • 對管道的其餘部分進行短路處理。

這樣 ResponseHeaderAttribute 篩選器就不會為 Index 操作運作。 如果這兩個篩選器都應用于操作方法級别,隻要 ShortCircuitingResourceFilterAttribute 先運作,此行為就不會變。 ShortCircuitingResourceFilterAttribute 因其篩選器類型而首先運作:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ShortCircuitingController : Controller
{
    [ShortCircuitingResourceFilter]
    public IActionResult Index() =>
        Content($"- {nameof(ShortCircuitingController)}.{nameof(Index)}");
}
           

依賴關系注入

可按類型或執行個體添加篩選器。 如果添加執行個體,該執行個體将用于每個請求。 如果添加類型,則将激活該類型。 激活類型的篩選器意味着:

  • 将為每個請求建立一個執行個體。
  • 依賴關系注入 (DI) 将填充所有構造函數依賴項。

如果将篩選器作為屬性實作并直接添加到控制器類或操作方法中,則該篩選器不能由依賴關系注入 (DI) 提供構造函數依賴項。 構造函數依賴項不能由 DI 提供,因為屬性在應用時必須提供自己的構造函數參數。

以下篩選器支援從 DI 提供的構造函數依賴項:

  • ServiceFilterAttribute
  • TypeFilterAttribute
  • 在屬性上實作 IFilterFactory。

可以将前面的篩選器應用于控制器或操作。

可以從 DI 擷取記錄器。 但是,避免建立和使用篩選器僅用于日志記錄。 内置架構日志記錄通常提供日志記錄所需的内容。 添加到篩選器的日志記錄:

  • 應重點關注業務域問題或特定于篩選器的行為。
  • 不應記錄操作或其他架構事件。 内置篩選器已記錄操作和架構事件。

ServiceFilterAttribute

在 Program.cs 中注冊服務篩選器實作類型。 ServiceFilterAttribute 可從 DI 檢索篩選器執行個體。

以下代碼顯示了使用 DI 的 LoggingResponseHeaderFilterService 類:

public class LoggingResponseHeaderFilterService : IResultFilter
{
    private readonly ILogger _logger;

    public LoggingResponseHeaderFilterService(
            ILogger<LoggingResponseHeaderFilterService> logger) =>
        _logger = logger;

    public void OnResultExecuting(ResultExecutingContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuting)}");

        context.HttpContext.Response.Headers.Add(
            nameof(OnResultExecuting), nameof(LoggingResponseHeaderFilterService));
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuted)}");
    }
}
           

在以下代碼中,LoggingResponseHeaderFilterService 将添加到 DI 容器中:

在以下代碼中,ServiceFilter 屬性将從 DI 中檢索 LoggingResponseHeaderFilterService 篩選器的執行個體:

[ServiceFilter(typeof(LoggingResponseHeaderFilterService))]
public IActionResult WithServiceFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithServiceFilter)}");
           

使用 ServiceFilterAttribute 時,設定 ServiceFilterAttribute.IsReusable:

  • 提供以下提示:篩選器執行個體可能在其建立的請求範圍之外被重用。 ASP.NET Core 運作時不保證:
    • 将建立篩選器的單一執行個體。
    • 稍後不會從 DI 容器重新請求篩選器。
  • 不應與依賴于具有除單一執行個體以外的生命周期的服務的篩選器一起使用。

ServiceFilterAttribute 可實作 IFilterFactory。 IFilterFactory 公開用于建立 IFilterMetadata 執行個體的 CreateInstance 方法。 CreateInstance 從 DI 中加載指定的類型。

TypeFilterAttribute

TypeFilterAttribute 與 ServiceFilterAttribute 類似,但不會直接從 DI 容器解析其類型。 它使用 Microsoft.Extensions.DependencyInjection.ObjectFactory 對類型進行執行個體化。

因為不會直接從 DI 容器解析 TypeFilterAttribute 類型:

  • 使用 TypeFilterAttribute 引用的類型不需要注冊在 DI 容器中。 它們具備由 DI 容器實作的依賴項。
  • TypeFilterAttribute 可以選擇為類型接受構造函數參數。

使用 TypeFilterAttribute 時,設定 TypeFilterAttribute.IsReusable:

  • 提供提示:篩選器執行個體可能在其建立的請求範圍之外被重用。 ASP.NET Core 運作時不保證将建立篩選器的單一執行個體。
  • 不應與依賴于生命周期不同于單一執行個體的服務的篩選器一起使用。

下面的示例示範如何使用 TypeFilterAttribute 将參數傳遞到類型:

[TypeFilter(typeof(LoggingResponseHeaderFilter),
    Arguments = new object[] { "Filter-Header", "Filter Value" })]
public IActionResult WithTypeFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithTypeFilter)}");
           

授權篩選器

授權篩選器:

  • 是篩選器管道中運作的第一個篩選器。
  • 控制對操作方法的通路。
  • 具有在它之前的執行的方法,但沒有之後執行的方法。

自定義授權篩選器需要自定義授權架構。 建議配置授權政策或編寫自定義授權政策,而不是編寫自定義篩選器。 内置授權篩選器:

  • 調用授權系統。
  • 不授權請求。

不會在授權篩選器中引發異常:

  • 不會處理異常。
  • 異常篩選器不會處理異常。

在授權篩選器出現異常時請小心應對。

資源篩選器

資源篩選器:

  • 實作 IResourceFilter 或 IAsyncResourceFilter 接口。
  • 執行會覆寫篩選器管道的絕大部分。
  • 隻有授權篩選器在資源篩選器之前運作。

如果要使大部分管道短路,資源篩選器會很有用。 例如,如果緩存命中,則緩存篩選器可以繞開管道的其餘階段。

資源篩選器示例:

  • 之前顯示的短路資源篩選器。
  • DisableFormValueModelBindingAttribute:
    • 可以防止模型綁定通路表單資料。
    • 用于上傳大型檔案,以防止表單資料被讀入記憶體。

操作篩選器

操作篩選器不應用于 Razor Pages。 Razor Pages 支援 IPageFilter 和 IAsyncPageFilter。 有關詳細資訊,請參閱 Razor Pages 的篩選方法。

操作篩選器:

  • 實作 IActionFilter 或 IAsyncActionFilter 接口。
  • 它們的執行圍繞着操作方法的執行。

以下代碼顯示示例操作篩選器:

public class SampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}
           

ActionExecutingContext 提供以下屬性:

  • ActionArguments - 用于讀取操作方法的輸入。
  • Controller - 用于處理控制器執行個體。
  • Result - 設定 Result 會使操作方法和後續操作篩選器的執行短路。

在操作方法中引發異常:

  • 防止運作後續篩選器。
  • 與設定 Result 不同,結果被視為失敗而不是成功。

ActionExecutedContext 提供 Controller 和 Result 以及以下屬性:

  • Canceled - 如果操作執行已被另一個篩選器設定短路,則為 true。
  • Exception - 如果操作或之前運作的操作篩選器引發了異常,則為非 NULL 值。 将此屬性設定為 null:
    • 有效地處理異常。
    • 執行 Result,從操作方法中将它傳回。

      對于 IAsyncActionFilter,一個向 ActionExecutionDelegate 的調用可以達到以下目的:

  • 執行所有後續操作篩選器和操作方法。
  • 傳回 ActionExecutedContext。

若要設定短路,可将 Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result 配置設定到某個結果執行個體,并且不調用 next (ActionExecutionDelegate)。

該架構提供一個可子類化的抽象 ActionFilterAttribute。

OnActionExecuting 操作篩選器可用于:

  • 驗證模型狀态。
  • 如果狀态無效,則傳回錯誤
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }
}
           
使用 [ApiController] 屬性注釋的控制器會自動驗證模型狀态并傳回 400 響應。

OnActionExecuted 方法在操作方法之後運作:

  • 可通過 Result 屬性檢視和處理操作結果。
  • 如果操作執行已被另一個篩選器設定短路,則 Canceled 設定為 true。
  • 如果操作或後續操作篩選器引發了異常,則 Exception 設定為非 NULL 值。 将 Exception 設定為 null:
    • 有效地處理異常。
    • 執行 ActionExecutedContext.Result,從操作方法中将它正常傳回。

異常篩選器

異常篩選器:

  • 實作 IExceptionFilter 或 IAsyncExceptionFilter。
  • 可用于實作常見的錯誤處理政策。

下面的異常篩選器示例顯示在開發應用時發生的異常的相關詳細資訊:

public class SampleExceptionFilter : IExceptionFilter
{
    private readonly IHostEnvironment _hostEnvironment;

    public SampleExceptionFilter(IHostEnvironment hostEnvironment) =>
        _hostEnvironment = hostEnvironment;

    public void OnException(ExceptionContext context)
    {
        if (!_hostEnvironment.IsDevelopment())
        {
            // Don't display exception details unless running in Development.
            return;
        }

        context.Result = new ContentResult
        {
            Content = context.Exception.ToString()
        };
    }
}
           

以下代碼測試異常篩選器:

[TypeFilter(typeof(SampleExceptionFilter))]
public class ExceptionController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(ExceptionController)}.{nameof(Index)}");
}
           

異常篩選器:

  • 沒有之前和之後的事件。
  • 實作 OnException 或 OnExceptionAsync。
  • 處理 Razor 頁面或控制器建立、模型綁定、操作篩選器或操作方法中發生的未經處理的異常。
  • 請不要捕獲資源篩選器、結果篩選器或 MVC 結果執行中發生的異常。

    若要處理異常,請将 ExceptionHandled 屬性設定為 true 或配置設定 Result 屬性。 這将停止傳播異常。 異常篩選器無法将異常轉變為“成功”。 隻有操作篩選器才能執行該轉變。

異常篩選器:

  • 非常适合捕獲發生在操作中的異常。
  • 并不像錯誤進行中間件那麼靈活。

建議使用中間件處理異常。 基于所調用的操作方法,僅當錯誤處理不同時,才使用異常篩選器。 例如,應用可能具有用于 API 終結點和視圖/HTML 的操作方法。 API 終結點可以将錯誤資訊傳回為 JSON,而基于視圖的操作可能會以 HTML 形式傳回錯誤頁。

結果篩選器

結果篩選器:

  • 實作接口:
    • IResultFilter 或 IAsyncResultFilter
    • IAlwaysRunResultFilter 或 IAsyncAlwaysRunResultFilter
  • 它們的執行圍繞着操作結果的執行。

IResultFilter 和 IAsyncResultFilter

以下代碼顯示示例結果篩選器:

public class SampleResultFilter : IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        // Do something before the result executes.
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Do something after the result executes.
    }
}
           

要執行的結果類型取決于所執行的操作。 傳回視圖的操作會将所有 Razor 處理作為要執行的 ViewResult 的一部分。 API 方法可能會将某些序列化操作作為結果執行的一部分。 詳細了解操作結果。

僅當操作或操作篩選器生成操作結果時,才會執行結果篩選器。 不會在以下情況下執行結果篩選器:

  • 授權篩選器或資源篩選器使管道短路。
  • 異常篩選器通過生成操作結果來處理異常。

Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting 方法可以将 Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel 設定為 true,使操作結果和後續結果篩選器的執行短路。 設定短路時寫入響應對象,以免生成空響應。 如果在 IResultFilter.OnResultExecuting 中引發異常,則會導緻:

  • 阻止操作結果和後續篩選器的執行。
  • 結果被視為失敗而不是成功。

當 Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted 方法運作時,響應可能已發送到用戶端。 如果響應已發送到用戶端,則無法更改。

如果操作結果執行已被另一個篩選器設定短路,則 ResultExecutedContext.Canceled 設定為 true。

如果操作結果或後續結果篩選器引發了異常,則 ResultExecutedContext.Exception 設定為非 NULL 值。 将 Exception 設定為 NULL 可有效地處理異常,并防止在管道的後續階段引發該異常。 處理結果篩選器中出現的異常時,沒有可靠的方法來将資料寫入響應。 如果在操作結果引發異常時标頭已重新整理到用戶端,則沒有任何可靠的機制可用于發送失敗代碼。

對于 IAsyncResultFilter,通過調用 ResultExecutionDelegate 上的 await next 可執行所有後續結果篩選器和操作結果。 若要設定短路,可将 ResultExecutingContext.Cancel 設定為 true,并且不調用 ResultExecutionDelegate:

public class SampleAsyncResultFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(
        ResultExecutingContext context, ResultExecutionDelegate next)
    {
        if (context.Result is not EmptyResult)
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }
    }
}
           

該架構提供一個可子類化的抽象 ResultFilterAttribute。 前面所示的 ResponseHeaderAttribute 類是一種結果篩選器屬性。

IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter

IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter 接口聲明了一個針對所有操作結果運作的 IResultFilter 實作。 這包括由以下對象生成的操作結果:

  • 設定短路的授權篩選器和資源篩選器。
  • 異常篩選器。

例如,當内容協商失敗時,以下篩選器始終運作并設定操作結果 (ObjectResult) 422 Unprocessable Entity 狀态代碼:

public class UnprocessableResultFilter : IAlwaysRunResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is StatusCodeResult statusCodeResult
            && statusCodeResult.StatusCode == StatusCodes.Status415UnsupportedMediaType)
        {
            context.Result = new ObjectResult("Unprocessable")
            {
                StatusCode = StatusCodes.Status422UnprocessableEntity
            };
        }
    }

    public void OnResultExecuted(ResultExecutedContext context) { }
}
           

IFilterFactory

IFilterFactory 可實作 IFilterMetadata。 是以,IFilterFactory 執行個體可在篩選器管道中的任意位置用作 IFilterMetadata 執行個體。 當運作時準備調用篩選器時,它會嘗試将其轉換為 IFilterFactory。 如果轉換成功,則調用 CreateInstance 方法來建立将調用的 IFilterMetadata 執行個體。 這提供了一種很靈活的設計,因為無需在應用啟動時顯式設定精确的篩選器管道。

IFilterFactory.IsReusable:

  • 是工廠的提示,即工廠建立的篩選器執行個體可以在其建立時的請求範圍之外重用。
  • 不應與依賴于具有除單一執行個體以外的生命周期的服務的篩選器一起使用。

ASP.NET Core 運作時不保證:

  • 将建立篩選器的單一執行個體。
  • 稍後不會從 DI 容器重新請求篩選器。
僅當篩選器的來源明确、篩選器是無狀态的,且篩選器可以安全地跨多個 HTTP 請求使用時,才将 IFilterFactory.IsReusable 配置為傳回 true。 例如,如果 IFilterFactory.IsReusable 傳回 true,則不從注冊為作用域或瞬态的 DI 傳回篩選器。

可以使用自定義屬性實作來實作 IFilterFactory 作為另一種建立篩選器的方法:

public class ResponseHeaderFilterFactory : Attribute, IFilterFactory
{
    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
        new InternalResponseHeaderFilter();

    private class InternalResponseHeaderFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context) =>
            context.HttpContext.Response.Headers.Add(
                nameof(OnActionExecuting), nameof(InternalResponseHeaderFilter));

        public void OnActionExecuted(ActionExecutedContext context) { }
    }
           

在以下代碼中應用了篩選器:

[ResponseHeaderFilterFactory]
public IActionResult Index() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(Index)}");
           

在屬性上實作 IFilterFactory

實作 IFilterFactory 的篩選器可用于以下篩選器:

  • 不需要傳遞參數。
  • 具備需要由 DI 填充的構造函數依賴項。

TypeFilterAttribute 可實作 IFilterFactory。 IFilterFactory 公開用于建立 IFilterMetadata 執行個體的 CreateInstance 方法。 CreateInstance 從服務容器 (DI) 中加載指定的類型。

public class SampleActionTypeFilterAttribute : TypeFilterAttribute
{
    public SampleActionTypeFilterAttribute()
         : base(typeof(InternalSampleActionFilter)) { }

    private class InternalSampleActionFilter : IActionFilter
    {
        private readonly ILogger<InternalSampleActionFilter> _logger;

        public InternalSampleActionFilter(ILogger<InternalSampleActionFilter> logger) =>
            _logger = logger;

        public void OnActionExecuting(ActionExecutingContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuting)}");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuted)}");
        }
    }
}
           

以下代碼顯示應用篩選器的三種方法:

[SampleActionTypeFilter]
public IActionResult WithDirectAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithDirectAttribute)}");

[TypeFilter(typeof(SampleActionTypeFilterAttribute))]
public IActionResult WithTypeFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithTypeFilterAttribute)}");

[ServiceFilter(typeof(SampleActionTypeFilterAttribute))]
public IActionResult WithServiceFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithServiceFilterAttribute)}");
           

在前面的代碼中,首選應用篩選器的第一種方法。

在篩選器管道中使用中間件

資源篩選器的工作方式與中間件類似,即涵蓋管道中的所有後續執行。 但篩選器又不同于中間件,它們是運作時的一部分,這意味着它們有權通路上下文和構造。

若要将中間件用作篩選器,可建立一個具有 Configure 方法的類型,該方法可指定要注入到篩選器管道的中間件。 以下示例使用中間件設定響應标頭:

public class FilterMiddlewarePipeline
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            context.Response.Headers.Add("Pipeline", "Middleware");

            await next();
        });
    }
}
           

使用 MiddlewareFilterAttribute 運作中間件:

[MiddlewareFilter(typeof(FilterMiddlewarePipeline))]
public class FilterMiddlewareController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(FilterMiddlewareController)}.{nameof(Index)}");
}
           

中間件篩選器與資源篩選器在篩選器管道的相同階段運作,即,在模型綁定之前以及管道的其餘階段之後。

線程安全

将篩選器的 執行個體 傳遞到 Add而不是其 Type中時,篩選器是單一執行個體, 不是 線程安全的。

對比中間件

一般來說,過濾器用于處理業務與應用程式的橫切關注點,用法和功能很像中間件,但過濾器允許你将作用範圍縮小,并将其插入到應用程式中有意義的位置,例如視圖之前或模型綁定之後。過濾器是 MVC 的一部分,可以通路其上下文和構造函數。例如,中間件很難檢測到請求的模型驗證是否産生錯誤,并且做出相應的響應。

來源

.net core學習——過濾器(Filter)

ASP.NET Core MVC 過濾器(Filter)

ASP.NET Core 中的篩選器

繼續閱讀