ASP.NET Core 中的篩選器


https://docs.microsoft.com/zh-cn/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2

通過使用 ASP.NET Core 中的篩選器,可在請求處理管道中的特定階段之前或之后運行代碼。

內置篩選器處理任務,例如:

  • 授權(防止用戶訪問未獲授權的資源)。
  • 響應緩存(對請求管道進行短路出路,以便返回緩存的響應)。

可以創建自定義篩選器,用於處理橫切關注點。 橫切關注點的示例包括錯誤處理、緩存、配置、授權和日志記錄。 篩選器可以避免復制代碼。 例如,錯誤處理異常篩選器可以合並錯誤處理。

本文檔適用於 Razor Pages、API 控制器和具有視圖的控制器。

查看或下載示例如何下載)。

篩選器的工作原理

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

請求通過其他中間件、路由中間件、操作選擇和 ASP.NET Core 操作調用管道進行處理。

篩選器類型

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

  • 授權篩選器最先運行,用於確定是否已針對請求為用戶授權。 如果請求未獲授權,授權篩選器可以讓管道短路。

  • 資源篩選器

    • 授權后運行。
    • OnResourceExecuting 可以在篩選器管道的其余階段之前運行代碼。 例如,OnResourceExecuting 可以在模型綁定之前運行代碼。
    • OnResourceExecuted 可以在管道的其余階段完成之后運行代碼。
  • 操作篩選器可以在調用單個操作方法之前和之后立即運行代碼。 它們可用於處理傳入某個操作的參數以及從該操作返回的結果。 不可在 Razor Pages 中使用操作篩選器。

  • 異常篩選器用於在向響應正文寫入任何內容之前,對未經處理的異常應用全局策略。

  • 結果篩選器可以在執行單個操作結果之前和之后立即運行代碼。 僅當操作方法成功執行時,它們才會運行。 對於必須圍繞視圖或格式化程序的執行的邏輯,它們很有用。

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

請求通過授權過濾器、資源過濾器、模型綁定、操作過濾器、操作執行和操作結果轉換、異常過濾器、結果過濾器和結果執行進行處理。

實現

通過不同的接口定義,篩選器同時支持同步和異步實現。

同步篩選器可以在其管道階段之前 (On-Stage-Executing) 和之后 (On-Stage-Executed) 運行代碼。 例如,OnActionExecuting 在調用操作方法之前調用。 OnActionExecuted 在操作方法返回之后調用。

C#
public class MySampleActionFilter : 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 方法:

C#
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)。 每個 On-Stage-ExecutionAsync 方法采用執行篩選器的管道階段的 FilterType-ExecutionDelegate

多個篩選器階段

可以在單個類中實現多個篩選器階段的接口。 例如,ActionFilterAttribute 類實現 IActionFilterIResultFilter 及其異步等效接口。

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

內置篩選器屬性

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

C#
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 標頭的名稱和值:

C#
[AddHeader("Author", "Joe Smith")]
public class SampleController : Controller { public IActionResult Index() { return Content("Examine the headers using the F12 developer tools."); } [ShortCircuitingResourceFilter] public IActionResult SomeResource() { return Content("Successful access to resource - header is set."); } 

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

篩選器屬性:

篩選器作用域和執行順序

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

  • 在操作上使用屬性。
  • 在控制器上使用屬性。
  • 所有控制器和操作的全局篩選器,如下面的代碼所示:
C#
public void ConfigureServices(IServiceCollection services) { services.AddMvc(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 }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } 

前面的代碼使用 MvcOptions.Filters 集合全局添加三個篩選器。

默認執行順序

當有同一類型的多個篩選器時,作用域可確定篩選器執行的默認順序。 全局篩選器涵蓋類篩選器。 類篩選器涵蓋方法篩選器。

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

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

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

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

此序列顯示:

  • 方法篩選器已嵌套在控制器篩選器中。
  • 控制器篩選器已嵌套在全局篩選器中。

控制器和 Razor 頁面級篩選器

繼承自 Controller 基類的每個控制器包括 Controller.OnActionExecutingController.OnActionExecutionAsync 和 Controller.OnActionExecuted OnActionExecuted 方法。這些方法:

  • 覆蓋為給定操作運行的篩選器。
  • OnActionExecuting 在所有操作篩選器之前調用。
  • OnActionExecuted 在所有操作篩選器之后調用。
  • OnActionExecutionAsync 在所有操作篩選器之前調用。 next 之后的篩選器中的代碼在操作方法之后運行。

例如,在下載示例中,啟動時全局應用 MySampleActionFilter

TestController

  • 將 SampleActionFilterAttribute ([SampleActionFilter]) 應用於 FilterTest2 操作。
  • 重寫 OnActionExecuting 和 OnActionExecuted
C#
public class TestController : Controller { [SampleActionFilter] public IActionResult FilterTest2() { return Content($"From FilterTest2"); } public override void OnActionExecuting(ActionExecutingContext context) { // Do something before the action executes. base.OnActionExecuting(context); } public override void OnActionExecuted(ActionExecutedContext context) { // Do something after the action executes. base.OnActionExecuted(context); } } 

導航到 https://localhost:5001/Test/FilterTest2 運行以下代碼:

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

對於 Razor Pages,請參閱通過重寫篩選器方法實現 Razor 頁面篩選器

重寫默認順序

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

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

可以使用構造函數參數設置 Order 屬性:

C#
[MyFilter(Name = "Controller Level Attribute", Order=1)] 

請考慮前面示例中所示的 3 個相同操作篩選器。 如果控制器和全局篩選器的 Order 屬性分別設置為 1 和 2,則會反轉執行順序。

TABLE 3
序列 篩選器作用域 Order 屬性 篩選器方法
1 方法 0 OnActionExecuting
2 控制器 1 OnActionExecuting
3 Global 2 OnActionExecuting
4 Global 2 OnActionExecuted
5 控制器 1 OnActionExecuted
6 方法 0 OnActionExecuted

在確定篩選器的運行順序時,Order 屬性重寫作用域。 先按順序對篩選器排序,然后使用作用域消除並列問題。 所有內置篩選器實現 IOrderedFilter 並將默認 Order 值設為 0。 對於內置篩選器,作用域會確定順序,除非將 Order 設為非零值。

取消和設置短路

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

C#
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) { } } 

在下面的代碼中,ShortCircuitingResourceFilter 和 AddHeader 篩選器都以 SomeResource 操作方法為目標。 ShortCircuitingResourceFilter

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

這樣 AddHeader 篩選器就不會為 SomeResource 操作運行。 如果這兩個篩選器都應用於操作方法級別,只要 ShortCircuitingResourceFilter 先運行,此行為就不會變。 先運行 ShortCircuitingResourceFilter(考慮到它的篩選器類型),或顯式使用 Order 屬性。

C#
[AddHeader("Author", "Joe Smith")]
public class SampleController : Controller { public IActionResult Index() { return Content("Examine the headers using the F12 developer tools."); } [ShortCircuitingResourceFilter] public IActionResult SomeResource() { return Content("Successful access to resource - header is set."); } 

依賴關系注入

可按類型或實例添加篩選器。 如果添加實例,該實例將用於每個請求。 如果添加類型,則將激活該類型。 激活類型的篩選器意味着:

  • 將為每個請求創建一個實例。
  • 依賴關系注入 (DI) 將填充所有構造函數依賴項。

如果將篩選器作為屬性實現並直接添加到控制器類或操作方法中,則該篩選器不能由依賴關系注入 (DI) 提供構造函數依賴項。 無法由 DI 提供構造函數依賴項,因為:

  • 屬性在應用時必須提供自己的構造函數參數。
  • 這是屬性工作原理上的限制。

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

可以將前面的篩選器應用於控制器或操作方法:

可以從 DI 獲取記錄器。 但是,避免創建和使用篩選器僅用於日志記錄。 內置框架日志記錄通常提供日志記錄所需的內容。 添加到篩選器的日志記錄:

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

ServiceFilterAttribute

在 ConfigureServices 中注冊服務篩選器實現類型。 ServiceFilterAttribute 可從 DI 檢索篩選器實例。

以下代碼顯示 AddHeaderResultServiceFilter

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

在以下代碼中,AddHeaderResultServiceFilter 將添加到 DI 容器中:

C#
public void ConfigureServices(IServiceCollection services) { // Add service filters. services.AddScoped<AddHeaderResultServiceFilter>(); services.AddScoped<SampleActionFilterAttribute>(); services.AddMvc(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 }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } 

在以下代碼中,ServiceFilter 屬性將從 DI 中檢索 AddHeaderResultServiceFilter 篩選器的實例:

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

使用 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 將參數傳遞到類型:

C#
[TypeFilter(typeof(LogConstantFilter),
 Arguments = new object[] { "Method 'Hi' called" })]
public IActionResult Hi(string name) { return Content($"Hi {name}"); } 
C#
public class LogConstantFilter : IActionFilter { private readonly string _value; private readonly ILogger<LogConstantFilter> _logger; public LogConstantFilter(string value, ILogger<LogConstantFilter> logger)  { _logger = logger; _value = value; } public void OnActionExecuting(ActionExecutingContext context) { _logger.LogInformation(_value); } public void OnActionExecuted(ActionExecutedContext context) { } } 

授權篩選器

授權篩選器:

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

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

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

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

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

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

詳細了解授權

資源篩選器

資源篩選器:

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

資源篩選器示例:

操作篩選器

 重要

操作篩選器不應用於 Razor Pages。 Razor Pages 支持 IPageFilter 和 IAsyncPageFilter有關詳細信息,請參閱 Razor 頁面的篩選方法

操作篩選器:

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

C#
public class MySampleActionFilter : 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 操作篩選器可用於:

  • 驗證模型狀態。
  • 如果狀態無效,則返回錯誤。
C#
public class ValidateModelAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { if (!context.ModelState.IsValid) { context.Result = new BadRequestObjectResult(context.ModelState); } } 

OnActionExecuted 方法在操作方法之后運行:

  • 可通過 Result 屬性查看和處理操作結果。

  • 如果操作執行已被另一個篩選器設置短路,則 Canceled 設置為 true。

  • 如果操作或后續操作篩選器引發了異常,則 Exception 設置為非 NULL 值。 將 Exception設置為 null:

    • 有效地處理異常。
    • 執行 ActionExecutedContext.Result,從操作方法中將它正常返回。
C#
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); } } 

異常篩選器

異常篩選器:

下面的異常篩選器示例使用自定義錯誤視圖,顯示在開發應用時發生的異常的相關詳細信息:

C#
public class CustomExceptionFilter : IExceptionFilter { private readonly IHostingEnvironment _hostingEnvironment; private readonly IModelMetadataProvider _modelMetadataProvider; public CustomExceptionFilter( IHostingEnvironment 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; } } 

異常篩選器:

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

若要處理異常,請將 ExceptionHandled 屬性設置為 true,或編寫響應。 這將停止傳播異常。異常篩選器無法將異常轉變為“成功”。 只有操作篩選器才能執行該轉變。

異常篩選器:

  • 非常適合捕獲發生在操作中的異常。
  • 並不像錯誤處理中間件那么靈活。

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

結果篩選器

結果篩選器:

IResultFilter 和 IAsyncResultFilter

以下代碼顯示一個添加 HTTP 標頭的結果篩選器:

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

要執行的結果類型取決於所執行的操作。 返回視圖的操作會將所有 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 可有效地處理異常,並防止 ASP.NET Core 在管道的后續階段重新引發該異常。 處理結果篩選器中出現的異常時,沒有可靠的方法來將數據寫入響應。 如果在操作結果引發異常時標頭已刷新到客戶端,則沒有任何可靠的機制可用於發送失敗代碼。

對於 IAsyncResultFilter,通過調用 ResultExecutionDelegate 上的 await next 可執行所有后續結果篩選器和操作結果。 若要設置短路,請將 ResultExecutingContext.Cancel 設置為 true,並且不調用 ResultExecutionDelegate

C#
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

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

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

例如,以下篩選器始終運行並在內容協商失敗時設置具有“422 無法處理的實體”狀態代碼的操作結果 (ObjectResult):

C#
public class UnprocessableResultFilter : Attribute, IAlwaysRunResultFilter { public void OnResultExecuting(ResultExecutingContext context) { if (context.Result is StatusCodeResult statusCodeResult && statusCodeResult.StatusCode == 415) { context.Result = new ObjectResult("Can't process this!") { StatusCode = 422, }; } } public void OnResultExecuted(ResultExecutedContext context) { } } 

IFilterFactory

IFilterFactory 可實現 IFilterMetadata。 因此,IFilterFactory 實例可在篩選器管道中的任意位置用作 IFilterMetadata 實例。 當運行時准備調用篩選器時,它會嘗試將其轉換為 IFilterFactory。 如果轉換成功,則調用 CreateInstance 方法來創建將調用的 IFilterMetadata 實例。 這提供了一種很靈活的設計,因為無需在應用啟動時顯式設置精確的篩選器管道。

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

C#
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; } } } 

可以通過運行下載示例來測試前面的代碼:

  • 調用 F12 開發人員工具。
  • 導航到 https://localhost:5001/Sample/HeaderWithFactory

F12 開發人員工具顯示示例代碼添加的以下響應標頭:

  • author: Joe Smith
  • globaladdheader: Result filter added to MvcOptions.Filters
  • internal: My header

前面的代碼創建 internal: My header 響應標頭。

在屬性上實現 IFilterFactory

實現 IFilterFactory 的篩選器可用於以下篩選器:

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

TypeFilterAttribute 可實現 IFilterFactory。 IFilterFactory 公開用於創建 IFilterMetadata 實例的 CreateInstance 方法。 CreateInstance 從服務容器 (DI) 中加載指定的類型。

C#
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("Business action starting..."); // perform some business logic work } public void OnActionExecuted(ActionExecutedContext context) { // perform some business logic work _logger.LogInformation("Business action completed."); } } } 

以下代碼顯示應用 [SampleActionFilter] 的三種方法:

C#
[SampleActionFilter]
public IActionResult FilterTest() { return Content($"From FilterTest"); } [TypeFilter(typeof(SampleActionFilterAttribute))] public IActionResult TypeFilterTest() { return Content($"From ServiceFilterTest"); } // 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 的首選方法。

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

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

若要將中間件用作篩選器,可創建一個具有 Configure 方法的類型,該方法可指定要注入到篩選器管道的中間件。 下面的示例使用本地化中間件為請求建立當前區域性:

C#
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 運行中間件:

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

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

后續操作


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM