ASP.NET Core 中的篩選條件

作者:Kirk Larkin \(英文\)、Rick Anderson \(英文\)、Tom Dykstra \(英文\) 及 Steve Smith \(英文\)

ASP.NET Core 中的篩選條件可讓程式碼在要求處理管線中的特定階段之前或之後執行。

內建篩選條件會處理工作,例如:

  • 授權,避免存取使用者未獲授權的資源。
  • 回應快取,縮短要求管線,傳回快取的回應。

可以建立自訂篩選條件來處理跨領域關注。 跨領域關注的範例包括錯誤處理、快取、設定、授權及記錄。 篩選能避免重複的程式碼。 例如,錯誤處理例外狀況篩選條件中可以合併錯誤處理。

本文件適用於 Razor Pages、API 控制器,以及具有檢視的控制器。 篩選無法直接與 Razor 元件搭配使用。 篩選條件只能在以下情況下間接影響元件:

  • 元件內嵌在頁面或檢視中。
  • 頁面或控制器和檢視會使用篩選。

篩選條件如何運作

篩選條件會在「ASP.NET Core 動作引動過程管線」中執行,其有時也被稱為「篩選條件管線」。 在 ASP.NET Core 之後執行的篩選條件管線會選取要執行的動作:

The request is processed through Other Middleware, Routing Middleware, Action Selection, and the Action Invocation Pipeline. The request processing continues back through Action Selection, Routing Middleware, and various Other Middleware before becoming a response sent to the client.

篩選類型

每個篩選類型會在篩選條件管線中的不同階段執行:

  • 授權篩選條件

    • 先執行。
    • 判斷使用者是否獲得此要求的授權。
    • 如果要求未獲得授權,則縮短管線的線路。
  • 資源篩選條件

    • 會在授權之後執行。
    • OnResourceExecuting 會在其餘的篩選條件管線之前執行程式碼。 例如,OnResourceExecuting 會在模型繫結之前執行程式碼。
    • OnResourceExecuted 會在其餘的管線皆已完成之後執行程式碼。
  • 動作篩選條件

    • 在呼叫動作方法之前和之後立即執行。
    • 可以變更傳遞至動作的引數。
    • 可以變更從動作傳回的結果。
    • Razor Pages 支援。
  • 端點篩選

    • 在呼叫動作方法之前和之後立即執行。
    • 可以變更傳遞至動作的引數。
    • 可以變更從動作傳回的結果。
    • Razor Pages 支援。
    • 可以在動作和以路由處理常式為基礎的端點上叫用。
  • 例外狀況篩選條件會將通用原則套用到在寫入回應主體之前發生的未處理例外狀況。

  • 結果篩選條件

    • 在執行動作結果之前和之後立即執行。
    • 只有當動作方法成功執行時,才會執行。
    • 適用於必須包圍檢視或格式器執行的邏輯。

下圖顯示篩選條件類型如何在篩選條件管線中互動:

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.

Razor Pages 也支援在 Razor Page 處理常式之前和之後執行的 Razor Page 篩選

實作

篩選條件同時支援透過不同介面定義的同步和非同步實作。

同步篩選可以在其管線階段之前和之後執行。 例如,系統會在呼叫動作方法之前呼叫 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 類別會實作:

請實作同步非同步版本的篩選條件介面,而不要同時實作這兩者。 執行階段會先檢查以查看篩選條件是否會實作非同步介面,如果是,便會呼叫該介面。 如果沒有,它會呼叫同步介面的方法。 如果同時在單一類別中實作非同步和同步介面,系統只會呼叫非同步方法。 使用抽象類別 (例如 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

有幾個篩選條件介面有對應的屬性,可用來作為自訂實作的基底類別。

篩選條件屬性:

篩選條件無法套用至 Razor Page 處理常式方法。 可以套用至 Razor Page 模型或全域套用。

篩選條件範圍和執行的順序

篩選條件能以三個「範圍」之一新增至管線:

  • 針對控制器或 Razor Page 使用屬性。
  • 針對控制器動作使用屬性。 篩選屬性無法套用至 Razor Pages 處理常式方法。
  • 可全域套用至所有控制器、動作和 Razor Page,如下列程式碼所示:
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllersWithViews(options =>
    {
        options.Filters.Add<GlobalSampleActionFilter>();
    });
    

預設執行順序

當管線的特定階段有多個篩選條件時,範圍會決定篩選條件的預設執行順序。 全域篩選條件會圍繞類別篩選條件,後者又圍繞方法篩選條件。

因為篩選條件巢狀結構的原因,篩選條件的「之後」程式碼的執行順序會與「之前」程式碼相反。 篩選條件序列:

  • 全域篩選條件的「之前」程式碼。
    • 控制器篩選條件的「之前」程式碼。
      • 動作方法篩選條件的「之前」程式碼。
      • 動作方法篩選條件的「之後」程式碼。
    • 控制器篩選條件的「之後」程式碼。
  • 全域篩選條件的「之後」程式碼。

下列範例說明針對同步動作篩選執行篩選方法的順序:

序列 篩選條件範圍 篩選條件方法
1 全域 OnActionExecuting
2 控制器 OnActionExecuting
3 動作 OnActionExecuting
4 動作 OnActionExecuted
5 控制器 OnActionExecuted
6 全域 OnActionExecuted

控制器層級篩選

繼承自 Controller 的每個控制器都包含 OnActionExecutingOnActionExecutionAsyncOnActionExecuted 方法。 這些方法會包裝針對指定動作執行的篩選條件:

  • OnActionExecuting 會在任何動作的篩選之前執行。
  • OnActionExecuted 會在動作的所有篩選之後執行。
  • OnActionExecutionAsync 會在任何動作的篩選之前執行。 呼叫 next 之後的程式碼會在動作的篩選之後執行。

下列 ControllerFiltersController 類別:

  • SampleActionFilterAttribute ([SampleActionFilter]) 套用至控制器。
  • 覆寫 OnActionExecutingOnActionExecuted
[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:<port>/ControllerFilters 執行下列程式碼:

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

控制器層級篩選會將 Order 屬性設定為 int.MinValue。 控制器層級篩選無法設定為在套用至方法的篩選之後執行。 下一節會說明順序。

針對 Razor Pages,請參閱覆寫篩選方法來實作 Razor Page 篩選

覆寫預設順序

可以藉由實作 IOrderedFilter 來覆寫預設執行序列。 IOrderedFilter 會公開 Order 屬性,其優先順序會高於範圍以決定執行順序。 具有較低 Order 值的篩選條件:

  • 在具有較高 Order 值的篩選條件之前執行「之前」程式碼。
  • 在具有較高 Order 值的篩選條件之後執行「之後」程式碼。

控制器層級篩選範例中,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 是動作篩選條件。
  • 縮短管線的其餘部分。

因此,Index 動作的 ResponseHeaderAttribute 篩選條件永遠不會執行。 如果這兩個篩選條件都套用在動作方法層級,此行為也會相同,假設 ShortCircuitingResourceFilterAttribute 先執行的話。 ShortCircuitingResourceFilterAttribute 會因為其篩選類型而先執行:

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

相依性插入

可以依類型或執行個體新增篩選條件。 如果新增執行個體,該執行個體將用於每個要求。 如果新增類型,其將會由類型啟動。 由類型啟動的篩選條件表示:

  • 系統會針對每個要求建立執行個體。
  • 任何建構函式相依性都會由相依性插入 (DI) 所填入。

實作為屬性並直接新增至控制器類別或動作方法的篩選條件,不能由相依性插入 (DI) 提供建構函式相依性。 建構函式相依性無法由 DI 提供,因為屬性必須在其套用位置提供其建構函式參數。

下列篩選條件支援由 DI 所提供的建構函式相依性:

上述篩選條件可以被套用至控制器或動作。

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 容器:

builder.Services.AddScoped<LoggingResponseHeaderFilterService>();

在下列程式碼中,ServiceFilter 屬性會從 DI 擷取 LoggingResponseHeaderFilterService 篩選條件的執行個體:

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

使用 ServiceFilterAttribute 時,設定 ServiceFilterAttribute.IsReusable

  • 能提供篩選條件執行個體「可能」可以在其建立的要求範圍之外重複使用的提示。 ASP.NET Core 執行階段並不保證:
    • 將會建立篩選條件的單一執行個體。
    • 將不會於稍後的時間從 DI 容器重新要求篩選條件。
  • 不應該搭配仰賴具有非單一資料庫存留期之服務的篩選條件使用。

ServiceFilterAttribute 會實作 IFilterFactoryIFilterFactory 會公開 CreateInstance 方法來建立 IFilterMetadata 執行個體。 CreateInstance 會從 DI 載入指定的類型。

TypeFilterAttribute

TypeFilterAttribute 類似於 ServiceFilterAttribute,但其類型不會直接從 DI 容器解析。 它會使用 Microsoft.Extensions.DependencyInjection.ObjectFactory 來具現化類型。

由於 TypeFilterAttribute 類型不會直接從 DI 容器解析:

  • 使用 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)}");

授權篩選條件

授權篩選條件:

  • 是篩選條件管線內最先執行的篩選條件。
  • 控制動作方法的存取。
  • 有之前的方法,但沒有之後的方法。

自訂授權篩選條件需要自訂的授權架構。 最好是設定授權原則或撰寫自訂授權原則,而不要撰寫自訂篩選條件。 內建的授權篩選條件:

  • 呼叫授權系統。
  • 不會授權要求。

不會在授權篩選條件內擲回例外狀況:

  • 該例外狀況將不會被處理。
  • 例外狀況篩選條件將不會處理例外狀況。

請考慮在例外狀況於授權篩選條件中發生時發出挑戰。

深入了解授權

資源篩選條件

資源篩選條件:

資源篩選條件很適合用來縮短大部分的管線。 比方說,快取篩選條件可以避免快取命中上的其餘管線。

資源篩選條件範例:

動作篩選條件

動作篩選條件不會套用到 Razor Pages。 Razor Pages 支援 IPageFilterIAsyncPageFilter。 如需詳細資訊,請參閱 Razor Pages 的篩選方法

動作篩選條件:

下列程式碼會顯示範例動作篩選條件:

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 提供 ControllerResult,加上下列屬性:

  • 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 回應。 如需詳細資訊,請參閱自動 HTTP 400 回應

OnActionExecuted 方法會在動作方法之後執行:

  • 並可以透過 Result 屬性查看及操作動作的結果。
  • Canceled 會在動作執行被另一個篩選條件縮短時被設為 true。
  • Exception 會在動作或後續的動作篩選條件擲回例外狀況時被設為非 Null 值。 將 Exception 設定為 null:
    • 實際上會「處理」例外狀況。
    • 執行 ActionExecutedContext.Result,彷彿它已正常地從動作方法傳回。

例外狀況篩選條件

例外狀況篩選條件:

下列範例例外狀況篩選會顯示在開發應用程式時發生的例外狀況詳細資料:

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<SampleExceptionFilter>]
public class ExceptionController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(ExceptionController)}.{nameof(Index)}");
}

例外狀況篩選條件:

  • 沒有之前和之後的事件。
  • 實作 OnExceptionOnExceptionAsync
  • 處理在 Razor Page 或控制器建立、模型繫結、動作篩選條件或動作方法中發生的未處理例外狀況。
  • 不會攔截在資源篩選條件、結果篩選條件或 MVC 結果執行中發生的例外狀況。

若要處理例外狀況,請將 ExceptionHandled 屬性設定為 true 或指派 Result 屬性。 這樣會阻止傳播例外狀況。 例外狀況篩選條件無法將例外狀況變成「成功」。 只有動作篩選條件可以這麼做。

例外狀況篩選條件:

  • 適合用於設陷在動作內發生的例外狀況。
  • 不像錯誤處理中介軟體那麼有彈性。

偏好使用中介軟體進行例外狀況處理。 只有在錯誤處理會根據所呼叫的動作方法而「不同」時才使用例外狀況篩選條件。 比方說,應用程式可能會有適用於 API 端點及檢視/HTML 的動作方法。 API 端點可能將錯誤資訊傳回為 JSON,而以檢視為基礎的動作則可能將錯誤頁面傳回為 HTML。

結果篩選條件

結果篩選條件:

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.
    }
}

執行的結果類型會取決於動作。 傳回檢視的動作會在執行中的 ViewResult 裡包含處理中的所有 Razor。 API 方法可能在結果執行當中執行某種序列化。 深入了解動作結果

只有在動作或動作篩選產生動作結果時,才會執行結果篩選。 在下列情況下不會執行結果篩選:

  • 授權篩選或資源篩選縮短管線。
  • 例外狀況篩選條件會產生動作結果來處理例外狀況。

藉由將 Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel 設為 trueMicrosoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting 方法可以縮短動作結果和後續結果篩選條件的執行。 在縮短時寫入至回應物件,以避免產生空的回應。 擲回 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

IAlwaysRunResultFilterIAsyncAlwaysRunResultFilter 介面會宣告針對所有動作結果執行的 IResultFilter 實作。 這包括下列動作所產生的動作結果:

  • 縮短的授權篩選和資源篩選。
  • 例外狀況篩選。

例如,下列篩選一律會執行,並會在內容交涉失敗時,為動作結果 (ObjectResult) 設定「422 無法處理的實體」狀態碼:

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 會實作 IFilterFactoryIFilterFactory 會公開 CreateInstance 方法來建立 IFilterMetadata 執行個體。 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<SampleActionTypeFilterAttribute>]
public IActionResult WithTypeFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithTypeFilterAttribute)}");

[ServiceFilter<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<FilterMiddlewarePipeline>]
public class FilterMiddlewareController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(FilterMiddlewareController)}.{nameof(Index)}");
}

中介軟體篩選條件與資源篩選條件在相同的篩選條件管線階段執行:模型繫結之前和管線的其餘部分之後。

執行緒安全

將篩選的實例傳遞至 Add (而不是其 Type) 時,篩選條件是單一類型且不是安全執行續。

其他資源

作者:Kirk Larkin \(英文\)、Rick Anderson \(英文\)、Tom Dykstra \(英文\) 及 Steve Smith \(英文\)

ASP.NET Core 中的篩選條件可讓程式碼在要求處理管線中的特定階段之前或之後執行。

內建篩選條件會處理工作,例如:

  • 授權,避免存取使用者未獲授權的資源。
  • 回應快取,縮短要求管線,傳回快取的回應。

可以建立自訂篩選條件來處理跨領域關注。 跨領域關注的範例包括錯誤處理、快取、設定、授權及記錄。 篩選能避免重複的程式碼。 例如,錯誤處理例外狀況篩選條件中可以合併錯誤處理。

本文件適用於 Razor Pages、API 控制器,以及具有檢視的控制器。 篩選無法直接與 Razor 元件搭配使用。 篩選條件只能在以下情況下間接影響元件:

  • 元件內嵌在頁面或檢視中。
  • 頁面或控制器和檢視會使用篩選。

篩選條件如何運作

篩選條件會在「ASP.NET Core 動作引動過程管線」中執行,其有時也被稱為「篩選條件管線」。 在 ASP.NET Core 之後執行的篩選條件管線會選取要執行的動作:

The request is processed through Other Middleware, Routing Middleware, Action Selection, and the Action Invocation Pipeline. The request processing continues back through Action Selection, Routing Middleware, and various Other Middleware before becoming a response sent to the client.

篩選類型

每個篩選類型會在篩選條件管線中的不同階段執行:

  • 授權篩選條件

    • 先執行。
    • 判斷使用者是否獲得此要求的授權。
    • 如果要求未獲得授權,則縮短管線的線路。
  • 資源篩選條件

    • 會在授權之後執行。
    • OnResourceExecuting 會在其餘的篩選條件管線之前執行程式碼。 例如,OnResourceExecuting 會在模型繫結之前執行程式碼。
    • OnResourceExecuted 會在其餘的管線皆已完成之後執行程式碼。
  • 動作篩選條件

    • 在呼叫動作方法之前和之後立即執行。
    • 可以變更傳遞至動作的引數。
    • 可以變更從動作傳回的結果。
    • Razor Pages 支援。
  • 端點篩選

    • 在呼叫動作方法之前和之後立即執行。
    • 可以變更傳遞至動作的引數。
    • 可以變更從動作傳回的結果。
    • Razor Pages 支援。
    • 可以在動作和以路由處理常式為基礎的端點上叫用。
  • 例外狀況篩選條件會將通用原則套用到在寫入回應主體之前發生的未處理例外狀況。

  • 結果篩選條件

    • 在執行動作結果之前和之後立即執行。
    • 只有當動作方法成功執行時,才會執行。
    • 適用於必須包圍檢視或格式器執行的邏輯。

下圖顯示篩選條件類型如何在篩選條件管線中互動:

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.

Razor Pages 也支援在 Razor Page 處理常式之前和之後執行的 Razor Page 篩選

實作

篩選條件同時支援透過不同介面定義的同步和非同步實作。

同步篩選可以在其管線階段之前和之後執行。 例如,系統會在呼叫動作方法之前呼叫 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 類別會實作:

請實作同步非同步版本的篩選條件介面,而不要同時實作這兩者。 執行階段會先檢查以查看篩選條件是否會實作非同步介面,如果是,便會呼叫該介面。 如果沒有,它會呼叫同步介面的方法。 如果同時在單一類別中實作非同步和同步介面,系統只會呼叫非同步方法。 使用抽象類別 (例如 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

有幾個篩選條件介面有對應的屬性,可用來作為自訂實作的基底類別。

篩選條件屬性:

篩選條件無法套用至 Razor Page 處理常式方法。 可以套用至 Razor Page 模型或全域套用。

篩選條件範圍和執行的順序

篩選條件能以三個「範圍」之一新增至管線:

  • 針對控制器或 Razor Page 使用屬性。
  • 針對控制器動作使用屬性。 篩選屬性無法套用至 Razor Pages 處理常式方法。
  • 可全域套用至所有控制器、動作和 Razor Page,如下列程式碼所示:
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllersWithViews(options =>
    {
        options.Filters.Add<GlobalSampleActionFilter>();
    });
    

預設執行順序

當管線的特定階段有多個篩選條件時,範圍會決定篩選條件的預設執行順序。 全域篩選條件會圍繞類別篩選條件,後者又圍繞方法篩選條件。

因為篩選條件巢狀結構的原因,篩選條件的「之後」程式碼的執行順序會與「之前」程式碼相反。 篩選條件序列:

  • 全域篩選條件的「之前」程式碼。
    • 控制器篩選條件的「之前」程式碼。
      • 動作方法篩選條件的「之前」程式碼。
      • 動作方法篩選條件的「之後」程式碼。
    • 控制器篩選條件的「之後」程式碼。
  • 全域篩選條件的「之後」程式碼。

下列範例說明針對同步動作篩選執行篩選方法的順序:

序列 篩選條件範圍 篩選條件方法
1 全域 OnActionExecuting
2 控制器 OnActionExecuting
3 動作 OnActionExecuting
4 動作 OnActionExecuted
5 控制器 OnActionExecuted
6 全域 OnActionExecuted

控制器層級篩選

繼承自 Controller 的每個控制器都包含 OnActionExecutingOnActionExecutionAsyncOnActionExecuted 方法。 這些方法會包裝針對指定動作執行的篩選條件:

  • OnActionExecuting 會在任何動作的篩選之前執行。
  • OnActionExecuted 會在動作的所有篩選之後執行。
  • OnActionExecutionAsync 會在任何動作的篩選之前執行。 呼叫 next 之後的程式碼會在動作的篩選之後執行。

下列 ControllerFiltersController 類別:

  • SampleActionFilterAttribute ([SampleActionFilter]) 套用至控制器。
  • 覆寫 OnActionExecutingOnActionExecuted
[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:<port>/ControllerFilters 執行下列程式碼:

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

控制器層級篩選會將 Order 屬性設定為 int.MinValue。 控制器層級篩選無法設定為在套用至方法的篩選之後執行。 下一節會說明順序。

針對 Razor Pages,請參閱覆寫篩選方法來實作 Razor Page 篩選

覆寫預設順序

可以藉由實作 IOrderedFilter 來覆寫預設執行序列。 IOrderedFilter 會公開 Order 屬性,其優先順序會高於範圍以決定執行順序。 具有較低 Order 值的篩選條件:

  • 在具有較高 Order 值的篩選條件之前執行「之前」程式碼。
  • 在具有較高 Order 值的篩選條件之後執行「之後」程式碼。

控制器層級篩選範例中,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 是動作篩選條件。
  • 縮短管線的其餘部分。

因此,Index 動作的 ResponseHeaderAttribute 篩選條件永遠不會執行。 如果這兩個篩選條件都套用在動作方法層級,此行為也會相同,假設 ShortCircuitingResourceFilterAttribute 先執行的話。 ShortCircuitingResourceFilterAttribute 會因為其篩選類型而先執行:

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

相依性插入

可以依類型或執行個體新增篩選條件。 如果新增執行個體,該執行個體將用於每個要求。 如果新增類型,其將會由類型啟動。 由類型啟動的篩選條件表示:

  • 系統會針對每個要求建立執行個體。
  • 任何建構函式相依性都會由相依性插入 (DI) 所填入。

實作為屬性並直接新增至控制器類別或動作方法的篩選條件,不能由相依性插入 (DI) 提供建構函式相依性。 建構函式相依性無法由 DI 提供,因為屬性必須在其套用位置提供其建構函式參數。

下列篩選條件支援由 DI 所提供的建構函式相依性:

上述篩選條件可以被套用至控制器或動作。

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 容器:

builder.Services.AddScoped<LoggingResponseHeaderFilterService>();

在下列程式碼中,ServiceFilter 屬性會從 DI 擷取 LoggingResponseHeaderFilterService 篩選條件的執行個體:

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

使用 ServiceFilterAttribute 時,設定 ServiceFilterAttribute.IsReusable

  • 能提供篩選條件執行個體「可能」可以在其建立的要求範圍之外重複使用的提示。 ASP.NET Core 執行階段並不保證:
    • 將會建立篩選條件的單一執行個體。
    • 將不會於稍後的時間從 DI 容器重新要求篩選條件。
  • 不應該搭配仰賴具有非單一資料庫存留期之服務的篩選條件使用。

ServiceFilterAttribute 會實作 IFilterFactoryIFilterFactory 會公開 CreateInstance 方法來建立 IFilterMetadata 執行個體。 CreateInstance 會從 DI 載入指定的類型。

TypeFilterAttribute

TypeFilterAttribute 類似於 ServiceFilterAttribute,但其類型不會直接從 DI 容器解析。 它會使用 Microsoft.Extensions.DependencyInjection.ObjectFactory 來具現化類型。

由於 TypeFilterAttribute 類型不會直接從 DI 容器解析:

  • 使用 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)}");

授權篩選條件

授權篩選條件:

  • 是篩選條件管線內最先執行的篩選條件。
  • 控制動作方法的存取。
  • 有之前的方法,但沒有之後的方法。

自訂授權篩選條件需要自訂的授權架構。 最好是設定授權原則或撰寫自訂授權原則,而不要撰寫自訂篩選條件。 內建的授權篩選條件:

  • 呼叫授權系統。
  • 不會授權要求。

不會在授權篩選條件內擲回例外狀況:

  • 該例外狀況將不會被處理。
  • 例外狀況篩選條件將不會處理例外狀況。

請考慮在例外狀況於授權篩選條件中發生時發出挑戰。

深入了解授權

資源篩選條件

資源篩選條件:

資源篩選條件很適合用來縮短大部分的管線。 比方說,快取篩選條件可以避免快取命中上的其餘管線。

資源篩選條件範例:

動作篩選條件

動作篩選條件不會套用到 Razor Pages。 Razor Pages 支援 IPageFilterIAsyncPageFilter。 如需詳細資訊,請參閱 Razor Pages 的篩選方法

動作篩選條件:

下列程式碼會顯示範例動作篩選條件:

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 提供 ControllerResult,加上下列屬性:

  • 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 回應。 如需詳細資訊,請參閱自動 HTTP 400 回應

OnActionExecuted 方法會在動作方法之後執行:

  • 並可以透過 Result 屬性查看及操作動作的結果。
  • Canceled 會在動作執行被另一個篩選條件縮短時被設為 true。
  • Exception 會在動作或後續的動作篩選條件擲回例外狀況時被設為非 Null 值。 將 Exception 設定為 null:
    • 實際上會「處理」例外狀況。
    • 執行 ActionExecutedContext.Result,彷彿它已正常地從動作方法傳回。

例外狀況篩選條件

例外狀況篩選條件:

下列範例例外狀況篩選會顯示在開發應用程式時發生的例外狀況詳細資料:

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)}");
}

例外狀況篩選條件:

  • 沒有之前和之後的事件。
  • 實作 OnExceptionOnExceptionAsync
  • 處理在 Razor Page 或控制器建立、模型繫結、動作篩選條件或動作方法中發生的未處理例外狀況。
  • 不會攔截在資源篩選條件、結果篩選條件或 MVC 結果執行中發生的例外狀況。

若要處理例外狀況,請將 ExceptionHandled 屬性設定為 true 或指派 Result 屬性。 這樣會阻止傳播例外狀況。 例外狀況篩選條件無法將例外狀況變成「成功」。 只有動作篩選條件可以這麼做。

例外狀況篩選條件:

  • 適合用於設陷在動作內發生的例外狀況。
  • 不像錯誤處理中介軟體那麼有彈性。

偏好使用中介軟體進行例外狀況處理。 只有在錯誤處理會根據所呼叫的動作方法而「不同」時才使用例外狀況篩選條件。 比方說,應用程式可能會有適用於 API 端點及檢視/HTML 的動作方法。 API 端點可能將錯誤資訊傳回為 JSON,而以檢視為基礎的動作則可能將錯誤頁面傳回為 HTML。

結果篩選條件

結果篩選條件:

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.
    }
}

執行的結果類型會取決於動作。 傳回檢視的動作會在執行中的 ViewResult 裡包含處理中的所有 Razor。 API 方法可能在結果執行當中執行某種序列化。 深入了解動作結果

只有在動作或動作篩選產生動作結果時,才會執行結果篩選。 在下列情況下不會執行結果篩選:

  • 授權篩選或資源篩選縮短管線。
  • 例外狀況篩選條件會產生動作結果來處理例外狀況。

藉由將 Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel 設為 trueMicrosoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting 方法可以縮短動作結果和後續結果篩選條件的執行。 在縮短時寫入至回應物件,以避免產生空的回應。 擲回 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

IAlwaysRunResultFilterIAsyncAlwaysRunResultFilter 介面會宣告針對所有動作結果執行的 IResultFilter 實作。 這包括下列動作所產生的動作結果:

  • 縮短的授權篩選和資源篩選。
  • 例外狀況篩選。

例如,下列篩選一律會執行,並會在內容交涉失敗時,為動作結果 (ObjectResult) 設定「422 無法處理的實體」狀態碼:

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 會實作 IFilterFactoryIFilterFactory 會公開 CreateInstance 方法來建立 IFilterMetadata 執行個體。 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) 時,篩選條件是單一類型且不是安全執行續。

其他資源

作者:Kirk Larkin \(英文\)、Rick Anderson \(英文\)、Tom Dykstra \(英文\) 及 Steve Smith \(英文\)

ASP.NET Core 中的「篩選條件」可讓程式碼在要求處理管線中的特定階段之前或之後執行。

內建篩選條件會處理工作,例如:

  • 授權,避免存取使用者未獲授權的資源。
  • 回應快取,縮短要求管線,傳回快取的回應。

可以建立自訂篩選條件來處理跨領域關注。 跨領域關注的範例包括錯誤處理、快取、設定、授權及記錄。 篩選能避免重複的程式碼。 例如,錯誤處理例外狀況篩選條件中可以合併錯誤處理。

本文件適用於 Razor Pages、API 控制器,以及具有檢視的控制器。 篩選無法直接與 Razor 元件搭配使用。 篩選條件只能在以下情況下間接影響元件:

  • 元件內嵌在頁面或檢視中。
  • 頁面或控制器和檢視會使用篩選。

檢視或下載範例 \(英文\) (如何下載)。

篩選條件如何運作

篩選條件會在「ASP.NET Core 動作引動過程管線」中執行,其有時也被稱為「篩選條件管線」。 在 ASP.NET Core 之後執行的篩選條件管線會選取要執行的動作。

The request is processed through Other Middleware, Routing Middleware, Action Selection, and the Action Invocation Pipeline. The request processing continues back through Action Selection, Routing Middleware, and various Other Middleware before becoming a response sent to the client.

篩選類型

每個篩選類型會在篩選條件管線中的不同階段執行:

  • 授權篩選條件會先執行,用來判斷使用者是否已針對要求取得授權。 如果要求未經授權,授權篩選條件便會縮短管線。

  • 資源篩選條件

    • 會在授權之後執行。
    • OnResourceExecuting 會在其餘的篩選條件管線之前執行程式碼。 例如,OnResourceExecuting 會在模型繫結之前執行程式碼。
    • OnResourceExecuted 會在其餘的管線皆已完成之後執行程式碼。
  • 動作篩選條件

    • 在呼叫動作方法之前和之後立即執行程式碼。
    • 可以變更傳遞至動作的引數。
    • 可以變更從動作傳回的結果。
    • Razor Pages 支援。
  • 例外狀況篩選條件會將通用原則套用到在寫入回應主體之前發生的未處理例外狀況。

  • 結果篩選條件可以緊接在執行個作結果之前和之後執行程式碼。 它們只有在動作方法執行成功時才執行。 它們適用於必須包圍檢視或格式器執行的邏輯。

下圖顯示篩選條件類型如何在篩選條件管線中互動。

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.

實作

篩選條件同時支援透過不同介面定義的同步和非同步實作。

同步篩選會在其管線階段之前和之後執行程式碼。 例如,系統會在呼叫動作方法之前呼叫 OnActionExecuting。 系統會在傳回動作方法之後呼叫 OnActionExecuted

public class MySampleActionFilter : IActionFilter 
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }
}

在上述程式碼中,MyDebug下載範例中的公用程式函式。

非同步篩選會定義 On-Stage-ExecutionAsync 方法。 例如 OnActionExecutionAsync

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

        // next() calls the action method.
        var resultContext = await next();
        // resultContext.Result is set.
        // Do something after the action executes.
    }
}

在上述程式碼中,SampleAsyncActionFilter 具有會執行動作方法的 ActionExecutionDelegate (next)。

多個篩選條件階段

可以在單一類別中實作多個篩選條件階段的介面。 例如,ActionFilterAttribute 類別會實作:

請實作同步非同步版本的篩選條件介面,而不要同時實作這兩者。 執行階段會先檢查以查看篩選條件是否會實作非同步介面,如果是,便會呼叫該介面。 如果沒有,它會呼叫同步介面的方法。 如果同時在單一類別中實作非同步和同步介面,系統只會呼叫非同步方法。 使用抽象類別 (例如 ActionFilterAttribute) 時,請僅覆寫每個篩選條件類型的同步方法或非同步方法。

內建篩選條件屬性

ASP.NET Core 包含內建的屬性型篩選條件,可對其進行子類別化和自訂。 例如,下列結果篩選條件會將標頭新增至回應:

public class AddHeaderAttribute : ResultFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public AddHeaderAttribute(string name, string value)
    {
        _name = name;
        _value = value;
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add( _name, new string[] { _value });
        base.OnResultExecuting(context);
    }
}

屬性可讓篩選條件接受引數,如上述範例所示。 將 AddHeaderAttribute 新增至控制器或動作方法,並指定 HTTP 標頭的名稱和值:

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

使用瀏覽器開發人員工具之類的工具來檢查標頭。 回應標頭底下會顯示 author: Rick Anderson

下列程式碼會實作 ActionFilterAttribute 來執行下列動作:

  • 從組態系統讀取標題和名稱。 不同於先前的範例,下列程式碼不需要將篩選參數新增至程式碼。
  • 將標題和名稱新增至回應標頭。
public class MyActionFilterAttribute : ActionFilterAttribute
{
    private readonly PositionOptions _settings;

    public MyActionFilterAttribute(IOptions<PositionOptions> options)
    {
        _settings = options.Value;
        Order = 1;
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(_settings.Title, 
                                                 new string[] { _settings.Name });
        base.OnResultExecuting(context);
    }
}

組態選項會透過選項模式組態系統中提供。 例如,從 appsettings.json 檔案:

{
  "Position": {
    "Title": "Editor",
    "Name": "Joe Smith"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

StartUp.ConfigureServices 中:

  • PositionOptions 類別會透過 "Position" 組態區域來新增到服務容器。
  • MyActionFilterAttribute 會新增至服務容器。
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(
             Configuration.GetSection("Position"));
    services.AddScoped<MyActionFilterAttribute>();

    services.AddControllersWithViews();
}

下列程式碼顯示 PositionOptions 類別:

public class PositionOptions
{
    public string Title { get; set; }
    public string Name { get; set; }
}

下列程式碼會將 MyActionFilterAttribute 套用至 Index2 方法:

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ServiceFilter(typeof(MyActionFilterAttribute))]
    public IActionResult Index2()
    {
        return Content("Header values by configuration.");
    }

回應標頭底下 (author: Rick Anderson),Editor: Joe Smith 會在呼叫 Sample/Index2 端點時顯示。

下列程式碼會將 MyActionFilterAttributeAddHeaderAttribute 套用至 Razor Page:

[AddHeader("Author", "Rick Anderson")]
[ServiceFilter(typeof(MyActionFilterAttribute))]
public class IndexModel : PageModel
{
    public void OnGet()
    {
    }
}

篩選條件無法套用至 Razor Page 處理常式方法。 可以套用至 Razor Page 模型或全域套用。

有幾個篩選條件介面有對應的屬性,可用來作為自訂實作的基底類別。

篩選條件屬性:

篩選條件範圍和執行的順序

篩選條件能以三個「範圍」之一新增至管線:

  • 針對控制器動作使用屬性。 篩選屬性無法套用至 Razor Pages 處理常式方法。
  • 針對控制器或 Razor Page 使用屬性。
  • 可全域套用至所有控制器、動作和 Razor Page,如下列程式碼所示:
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter));
    });
}

預設執行順序

當管線的特定階段有多個篩選條件時,範圍會決定篩選條件的預設執行順序。 全域篩選條件會圍繞類別篩選條件,後者又圍繞方法篩選條件。

因為篩選條件巢狀結構的原因,篩選條件的「之後」程式碼的執行順序會與「之前」程式碼相反。 篩選條件序列:

  • 全域篩選條件的「之前」程式碼。
    • 控制器和 Razor Page 篩選條件的「之前」程式碼。
      • 動作方法篩選條件的「之前」程式碼。
      • 動作方法篩選條件的「之後」程式碼。
    • 控制器和 Razor Page 篩選條件的「之後」程式碼。
  • 全域篩選條件的「之後」程式碼。

下列範例說明針對同步動作篩選條件呼叫篩選條件方法的順序。

序列 篩選條件範圍 篩選條件方法
1 全域 OnActionExecuting
2 控制器或 Razor Page OnActionExecuting
3 方法 OnActionExecuting
4 方法 OnActionExecuted
5 控制器或 Razor Page OnActionExecuted
6 全域 OnActionExecuted

控制器層級篩選

繼承自 Controller 基底類別的每個控制器,都包含 Controller.OnActionExecutingController.OnActionExecutionAsyncController.OnActionExecutedOnActionExecuted 方法。 這些方法會:

  • 包裝針對指定動作執行的篩選條件。
  • 系統會在呼叫任何動作的篩選條件之前呼叫 OnActionExecuting
  • 系統會在呼叫所有動作篩選條件之後呼叫 OnActionExecuted
  • 系統會在呼叫任何動作的篩選條件之前呼叫 OnActionExecutionAsync。 在動作方法之後執行 next 後,位於篩選條件中的程式碼。

例如,在下載範例中,MySampleActionFilter 會在啟動時全域套用。

TestController

  • SampleActionFilterAttribute ([SampleActionFilter]) 套用至 FilterTest2 動作。
  • 覆寫 OnActionExecutingOnActionExecuted
public class TestController : Controller
{
    [SampleActionFilter(Order = int.MinValue)]
    public IActionResult FilterTest2()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuted(context);
    }
}

MyDisplayRouteInfo 是由 Rick.Docs.Samples.RouteInfo NuGet 套件提供,並顯示路由資訊。

瀏覽至 https://localhost:5001/Test/FilterTest2 執行下列程式碼:

  • TestController.OnActionExecuting
    • MySampleActionFilter.OnActionExecuting
      • SampleActionFilterAttribute.OnActionExecuting
        • TestController.FilterTest2
      • SampleActionFilterAttribute.OnActionExecuted
    • MySampleActionFilter.OnActionExecuted
  • TestController.OnActionExecuted

控制器層級篩選會將 Order 屬性設定為 int.MinValue。 控制器層級篩選無法設定為在套用至方法的篩選之後執行。 下一節會說明順序。

針對 Razor Pages,請參閱覆寫篩選方法來實作 Razor Page 篩選

覆寫預設順序

可以藉由實作 IOrderedFilter 來覆寫預設執行序列。 IOrderedFilter 會公開 Order 屬性,其優先順序會高於範圍以決定執行順序。 具有較低 Order 值的篩選條件:

  • 在具有較高 Order 值的篩選條件之前執行「之前」程式碼。
  • 在具有較高 Order 值的篩選條件之後執行「之後」程式碼。

Order 屬性可以搭配建構函式參數設定:

[SampleActionFilter(Order = int.MinValue)]

請考慮下列控制器中的兩個動作篩選:

[MyAction2Filter]
public class Test2Controller : Controller
{
    public IActionResult FilterTest2()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuted(context);
    }
}

StartUp.ConfigureServices 中會新增全域篩選:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter));
    });
}

3 個篩選條件會依下列循序執行:

  • Test2Controller.OnActionExecuting
    • MySampleActionFilter.OnActionExecuting
      • MyAction2FilterAttribute.OnActionExecuting
        • Test2Controller.FilterTest2
      • MyAction2FilterAttribute.OnResultExecuting
    • MySampleActionFilter.OnActionExecuted
  • Test2Controller.OnActionExecuted

Order 屬性在決定篩選條件執行的順序時會覆寫範圍。 篩選條件會先依照順序排序,然後使用範圍來打破僵局。 所有內建的篩選條件都會實作 IOrderedFilter 並將預設 Order 值設為 0。 如先前所述,控制器層級篩選會將 Order 屬性設定為 int.MinValue 針對內建篩選,範圍會決定順序,除非 Order 設定為非零值。

在上述程式碼中,MySampleActionFilter 具有全域範圍,因此會在具有控制器範圍的 MyAction2FilterAttribute 之前執行。 若要先執行 MyAction2FilterAttribute,請將順序設定為 int.MinValue

[MyAction2Filter(int.MinValue)]
public class Test2Controller : Controller
{
    public IActionResult FilterTest2()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuted(context);
    }
}

若要先執行全域篩選 MySampleActionFilter,請將 Order 設定為 int.MinValue

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

取消和縮短

可以設定提供給篩選條件方法之 ResourceExecutingContext 參數上的 Result 屬性,以縮短篩選條件管線。 比方說,下列資源篩選條件可防止管線的其餘部分執行:

public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.Result = new ContentResult()
        {
            Content = "Resource unavailable - header not set."
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}

在下列程式碼中,ShortCircuitingResourceFilterAddHeader 篩選條件都以 SomeResource 動作方法為目標。 ShortCircuitingResourceFilter

  • 最先執行,因為它是資源篩選條件,且 AddHeader 是動作篩選條件。
  • 縮短管線的其餘部分。

因此,SomeResource 動作的 AddHeader 篩選條件永遠不會執行。 如果這兩個篩選條件都套用在動作方法層級,此行為也會相同,假設 ShortCircuitingResourceFilter 先執行的話。 ShortCircuitingResourceFilter 會因為其篩選器類型而先執行,或藉由明確使用 Order 屬性而先執行。

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ServiceFilter(typeof(MyActionFilterAttribute))]
    public IActionResult Index2()
    {
        return Content("Header values by configuration.");
    }

    [ShortCircuitingResourceFilter]
    public IActionResult SomeResource()
    {
        return Content("Successful access to resource - header is set.");
    }

    [AddHeaderWithFactory]
    public IActionResult HeaderWithFactory()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }
}

相依性插入

可以依類型或執行個體新增篩選條件。 如果新增執行個體,該執行個體將用於每個要求。 如果新增類型,其將會由類型啟動。 由類型啟動的篩選條件表示:

  • 系統會針對每個要求建立執行個體。
  • 任何建構函式相依性都會由相依性插入 (DI) 所填入。

實作為屬性並直接新增至控制器類別或動作方法的篩選條件,不能由相依性插入 (DI) 提供建構函式相依性。 DI 無法提供建構函式相依性,因為:

  • 必須在提供屬性之建構函式參數的地方提供它們。
  • 這是屬性運作方式的限制。

下列篩選條件支援由 DI 所提供的建構函式相依性:

上述篩選條件可以被套用至控制器或動作方法:

DI 會提供記錄器。 不過,請避免僅針對記錄目的建立並使用篩選條件。 內建架構記錄通常便可以提供記錄所需的項目。 新增至篩選條件的記錄:

  • 應該專注在篩選條件特定的商務領域考量或行為。
  • 應該記錄動作或其他架構事件。 內建篩選條件能記錄動作和架構事件。

ServiceFilterAttribute

服務篩選條件實作類型會註冊在 ConfigureServices 中。 ServiceFilterAttribute 會從 DI 擷取篩選條件的執行個體。

下面程式碼會說明 AddHeaderResultServiceFilter

public class AddHeaderResultServiceFilter : IResultFilter
{
    private ILogger _logger;
    public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        var headerName = "OnResultExecuting";
        context.HttpContext.Response.Headers.Add(
            headerName, new string[] { "ResultExecutingSuccessfully" });
        _logger.LogInformation("Header added: {HeaderName}", headerName);
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Can't add to headers here because response has started.
        _logger.LogInformation("AddHeaderResultServiceFilter.OnResultExecuted");
    }
}

在下列程式碼中,會將 AddHeaderResultServiceFilter 新增至 DI 容器:

public void ConfigureServices(IServiceCollection services)
{
    // Add service filters.
    services.AddScoped<AddHeaderResultServiceFilter>();
    services.AddScoped<SampleActionFilterAttribute>();

    services.AddControllersWithViews(options =>
   {
       options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
           "Result filter added to MvcOptions.Filters"));         // An instance
        options.Filters.Add(typeof(MySampleActionFilter));         // By type
        options.Filters.Add(new SampleGlobalActionFilter());       // An instance
    });
}

在下列程式碼中,ServiceFilter 屬性會從 DI 擷取 AddHeaderResultServiceFilter 篩選條件的執行個體:

[ServiceFilter(typeof(AddHeaderResultServiceFilter))]
public IActionResult Index()
{
    return View();
}

使用 ServiceFilterAttribute 時,設定 ServiceFilterAttribute.IsReusable

  • 能提供篩選條件執行個體「可能」可以在其建立的要求範圍之外重複使用的提示。 ASP.NET Core 執行階段並不保證:

    • 將會建立篩選條件的單一執行個體。
    • 將不會於稍後的時間從 DI 容器重新要求篩選條件。
  • 不應該搭配仰賴具有非單一資料庫存留期之服務的篩選條件使用。

ServiceFilterAttribute 會實作 IFilterFactoryIFilterFactory 會公開 CreateInstance 方法來建立 IFilterMetadata 執行個體。 CreateInstance 會從 DI 載入指定的類型。

TypeFilterAttribute

TypeFilterAttribute 類似於 ServiceFilterAttribute,但其類型不會直接從 DI 容器解析。 它會使用 Microsoft.Extensions.DependencyInjection.ObjectFactory 來具現化類型。

由於 TypeFilterAttribute 類型不會直接從 DI 容器解析:

  • 使用 TypeFilterAttribute 參考的類型不需要向 DI 容器註冊。 不過它們的相依性會由 DI 容器滿足。
  • TypeFilterAttribute 可以選擇性地接受類型的建構函式引數。

使用 TypeFilterAttribute 時,設定 TypeFilterAttribute.IsReusable

  • 能提供篩選條件執行個體「可能」可以在其建立的要求範圍之外重複使用的提示。 ASP.NET Core 執行階段不保證將建立篩選條件的單一執行個體。

  • 不應該搭配仰賴具有非單一資料庫存留期之服務的篩選條件使用。

下列範例示範如何使用 TypeFilterAttribute 將引數傳遞至類型:

[TypeFilter(typeof(LogConstantFilter),
    Arguments = new object[] { "Method 'Hi' called" })]
public IActionResult Hi(string name)
{
    return Content($"Hi {name}");
}

授權篩選條件

授權篩選條件:

  • 是篩選條件管線內最先執行的篩選條件。
  • 控制動作方法的存取。
  • 有之前的方法,但沒有之後的方法。

自訂授權篩選條件需要自訂的授權架構。 最好是設定授權原則或撰寫自訂授權原則,而不要撰寫自訂篩選條件。 內建的授權篩選條件:

  • 呼叫授權系統。
  • 不會授權要求。

不會在授權篩選條件內擲回例外狀況:

  • 該例外狀況將不會被處理。
  • 例外狀況篩選條件將不會處理例外狀況。

請考慮在例外狀況於授權篩選條件中發生時發出挑戰。

深入了解授權

資源篩選條件

資源篩選條件:

資源篩選條件很適合用來縮短大部分的管線。 比方說,快取篩選條件可以避免快取命中上的其餘管線。

資源篩選條件範例:

動作篩選條件

動作篩選條件不會套用到 Razor Pages。 Razor Pages 支援 IPageFilterIAsyncPageFilter。 如需詳細資訊,請參閱 Razor Pages 的篩選方法

動作篩選條件:

下列程式碼會顯示範例動作篩選條件:

public class MySampleActionFilter : IActionFilter 
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }
}

ActionExecutingContext 提供下列屬性:

  • ActionArguments - 允許讀取動作方法的輸入。
  • Controller - 提供對控制器執行個體的管理。
  • Result - 設定 Result 會縮短動作方法和後續動作篩選條件的執行。

在動作方法中擲回例外狀況:

  • 防止執行後續篩選條件。
  • 和設定 Result 不同,會被視為失敗而非成功結果。

ActionExecutedContext 提供 ControllerResult,加上下列屬性:

  • 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 回應。 如需詳細資訊,請參閱自動 HTTP 400 回應OnActionExecuted 方法會在動作方法之後執行:

  • 並可以透過 Result 屬性查看及操作動作的結果。

  • Canceled 會在動作執行被另一個篩選條件縮短時被設為 true。

  • Exception 會在動作或後續的動作篩選條件擲回例外狀況時被設為非 Null 值。 將 Exception 設定為 null:

    • 實際上會「處理」例外狀況。
    • 執行 ActionExecutedContext.Result,彷彿它已正常地從動作方法傳回。
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext 
                                           context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(
                                                context.ModelState);
        }
    }


    public override void OnActionExecuted(ActionExecutedContext 
                                          context)
    {
        var result = context.Result;
        // Do something with Result.
        if (context.Canceled == true)
        {
            // Action execution was short-circuited by another filter.
        }

        if(context.Exception != null)
        {
            // Exception thrown by action or action filter.
            // Set to null to handle the exception.
            context.Exception = null;
        }
        base.OnActionExecuted(context);
    }
}

例外狀況篩選條件

例外狀況篩選條件:

下列範例例外狀況篩選條件會使用自訂的錯誤檢視,以顯示在開發應用程式時發生的例外狀況詳細資料:

public class CustomExceptionFilter : IExceptionFilter
{
    private readonly IWebHostEnvironment _hostingEnvironment;
    private readonly IModelMetadataProvider _modelMetadataProvider;

    public CustomExceptionFilter(
        IWebHostEnvironment hostingEnvironment,
        IModelMetadataProvider modelMetadataProvider)
    {
        _hostingEnvironment = hostingEnvironment;
        _modelMetadataProvider = modelMetadataProvider;
    }

    public void OnException(ExceptionContext context)
    {
        if (!_hostingEnvironment.IsDevelopment())
        {
            return;
        }
        var result = new ViewResult {ViewName = "CustomError"};
        result.ViewData = new ViewDataDictionary(_modelMetadataProvider,
                                                    context.ModelState);
        result.ViewData.Add("Exception", context.Exception);
        // TODO: Pass additional detailed data via ViewData
        context.Result = result;
    }
}

下列程式碼會測試例外狀況篩選:

[TypeFilter(typeof(CustomExceptionFilter))]
public class FailingController : Controller
{
    [AddHeader("Failing Controller", 
               "Won't appear when exception is handled")]
    public IActionResult Index()
    {
        throw new Exception("Testing custom exception filter.");
    }
}

例外狀況篩選條件:

  • 沒有之前和之後的事件。
  • 實作 OnExceptionOnExceptionAsync
  • 處理在 Razor Page 或控制器建立、模型繫結、動作篩選條件或動作方法中發生的未處理例外狀況。
  • 不會攔截在資源篩選條件、結果篩選條件或 MVC 結果執行中發生的例外狀況。

若要處理例外狀況,請將 ExceptionHandled 屬性設定為 true 或指派 Result 屬性。 這樣會阻止傳播例外狀況。 例外狀況篩選條件無法將例外狀況變成「成功」。 只有動作篩選條件可以這麼做。

例外狀況篩選條件:

  • 適合用於設陷在動作內發生的例外狀況。
  • 不像錯誤處理中介軟體那麼有彈性。

偏好使用中介軟體進行例外狀況處理。 只有在錯誤處理會根據所呼叫的動作方法而「不同」時才使用例外狀況篩選條件。 比方說,應用程式可能會有適用於 API 端點及檢視/HTML 的動作方法。 API 端點可能將錯誤資訊傳回為 JSON,而以檢視為基礎的動作則可能將錯誤頁面傳回為 HTML。

結果篩選條件

結果篩選條件:

IResultFilter 和 IAsyncResultFilter

以下程式碼會顯示能新增 HTTP 標頭的結果篩選條件:

public class AddHeaderResultServiceFilter : IResultFilter
{
    private ILogger _logger;
    public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        var headerName = "OnResultExecuting";
        context.HttpContext.Response.Headers.Add(
            headerName, new string[] { "ResultExecutingSuccessfully" });
        _logger.LogInformation("Header added: {HeaderName}", headerName);
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Can't add to headers here because response has started.
        _logger.LogInformation("AddHeaderResultServiceFilter.OnResultExecuted");
    }
}

執行的結果類型會取決於動作。 傳回檢視的動作會在執行中的 ViewResult 裡包含處理中的所有 Razor。 API 方法可能在結果執行當中執行某種序列化。 深入了解動作結果

只有在動作或動作篩選產生動作結果時,才會執行結果篩選。 在下列情況下不會執行結果篩選:

  • 授權篩選或資源篩選縮短管線。
  • 例外狀況篩選條件會產生動作結果來處理例外狀況。

藉由將 Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel 設為 trueMicrosoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting 方法可以縮短動作結果和後續結果篩選條件的執行。 在縮短時寫入至回應物件,以避免產生空的回應。 擲回 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 MyAsyncResponseFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(ResultExecutingContext context,
                                             ResultExecutionDelegate next)
    {
        if (!(context.Result is EmptyResult))
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }

    }
}

架構提供抽象 ResultFilterAttribute,其可被子類別化。 先前所示的 AddHeaderAttribute 類別是結果篩選條件屬性的範例。

IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter

IAlwaysRunResultFilterIAsyncAlwaysRunResultFilter 介面會宣告針對所有動作結果執行的 IResultFilter 實作。 這包括下列動作所產生的動作結果:

  • 縮短的授權篩選和資源篩選。
  • 例外狀況篩選。

例如,下列篩選一律會執行,並會在內容交涉失敗時,為動作結果 (ObjectResult) 設定「422 無法處理的實體」狀態碼:

public class UnprocessableResultFilter : Attribute, IAlwaysRunResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is StatusCodeResult statusCodeResult &&
            statusCodeResult.StatusCode == (int) HttpStatusCode.UnsupportedMediaType)
        {
            context.Result = new ObjectResult("Can't process this!")
            {
                StatusCode = (int) HttpStatusCode.UnsupportedMediaType,
            };
        }
    }

    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 AddHeaderWithFactoryAttribute : Attribute, IFilterFactory
{
    // Implement IFilterFactory
    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        return new InternalAddHeaderFilter();
    }

    private class InternalAddHeaderFilter : IResultFilter
    {
        public void OnResultExecuting(ResultExecutingContext context)
        {
            context.HttpContext.Response.Headers.Add(
                "Internal", new string[] { "My header" });
        }

        public void OnResultExecuted(ResultExecutedContext context)
        {
        }
    }

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

篩選條件會在下列程式碼中套用:

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ServiceFilter(typeof(MyActionFilterAttribute))]
    public IActionResult Index2()
    {
        return Content("Header values by configuration.");
    }

    [ShortCircuitingResourceFilter]
    public IActionResult SomeResource()
    {
        return Content("Successful access to resource - header is set.");
    }

    [AddHeaderWithFactory]
    public IActionResult HeaderWithFactory()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }
}

執行下載範例來測試上述程式碼:

  • 叫用 F12 開發人員工具。
  • 瀏覽至 https://localhost:5001/Sample/HeaderWithFactory

F12 開發人員工具會顯示由範例程式碼所加入的下列回應標頭:

  • author:Rick Anderson
  • globaladdheader:Result filter added to MvcOptions.Filters
  • internal:My header

上述程式碼會建立 internal:My header 回應標頭。

在屬性上實作的 IFilterFactory

實作 IFilterFactory 的篩選條件很適合用於下列類型的篩選條件:

  • 不需要傳遞參數。
  • 有需要由 DI 滿足的建構函式相依性。

TypeFilterAttribute 會實作 IFilterFactoryIFilterFactory 會公開 CreateInstance 方法來建立 IFilterMetadata 執行個體。 CreateInstance 會從服務容器 (DI) 載入指定的類型。

public class SampleActionFilterAttribute : TypeFilterAttribute
{
    public SampleActionFilterAttribute()
                         :base(typeof(SampleActionFilterImpl))
    { 
    }

    private class SampleActionFilterImpl : IActionFilter
    {
        private readonly ILogger _logger;
        public SampleActionFilterImpl(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
           _logger.LogInformation("SampleActionFilterAttribute.OnActionExecuting");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogInformation("SampleActionFilterAttribute.OnActionExecuted");
        }
    }
}

下列程式碼會顯示套用 [SampleActionFilter] 的三種方式:

[SampleActionFilter]
public IActionResult FilterTest()
{
    return Content("From FilterTest");
}

[TypeFilter(typeof(SampleActionFilterAttribute))]
public IActionResult TypeFilterTest()
{
    return Content("From TypeFilterTest");
}

// ServiceFilter must be registered in ConfigureServices or
// System.InvalidOperationException: No service for type '<filter>'
// has been registered. Is thrown.
[ServiceFilter(typeof(SampleActionFilterAttribute))]
public IActionResult ServiceFilterTest()
{
    return Content("From ServiceFilterTest");
}

在上述程式碼中,使用 [SampleActionFilter] 來裝飾方法是套用 SampleActionFilter 的建議方法。

在篩選條件管線中使用中介軟體

資源篩選條件的運作與中介軟體相似之處在於,它們會圍繞管線中稍後的所有項目執行。 但篩選條件與中介軟體不同之處在於,它們是執行階段的一部分,這表示它們能存取內容和建構。

若要使用中介軟體作為篩選條件,請建立一個具有 Configure 方法的類型,其能指定要插入到篩選條件管線的中介軟體。 以下範例會使用當地語系化中介軟體來建立某個要求目前的文化特性:

public class LocalizationPipeline
{
    public void Configure(IApplicationBuilder applicationBuilder)
    {
        var supportedCultures = new[]
        {
            new CultureInfo("en-US"),
            new CultureInfo("fr")
        };

        var options = new RequestLocalizationOptions
        {
            DefaultRequestCulture = new RequestCulture(
                                       culture: "en-US", 
                                       uiCulture: "en-US"),
            SupportedCultures = supportedCultures,
            SupportedUICultures = supportedCultures
        };
        options.RequestCultureProviders = new[] 
            { new RouteDataRequestCultureProvider() {
                Options = options } };

        applicationBuilder.UseRequestLocalization(options);
    }
}

使用 MiddlewareFilterAttribute 來執行中介軟體:

[Route("{culture}/[controller]/[action]")]
[MiddlewareFilter(typeof(LocalizationPipeline))]
public IActionResult CultureFromRouteData()
{
    return Content(
          $"CurrentCulture:{CultureInfo.CurrentCulture.Name},"
        + $"CurrentUICulture:{CultureInfo.CurrentUICulture.Name}");
}

中介軟體篩選條件與資源篩選條件在相同的篩選條件管線階段執行:模型繫結之前和管線的其餘部分之後。

執行緒安全

將篩選的實例傳遞至 Add (而不是其 Type) 時,篩選條件是單一類型且不是安全執行續。

後續動作