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 選擇了要執行的操作之后運行。
篩選器類型
每種篩選器類型都在篩選器管道中的不同階段執行:
-
授權篩選器最先運行,用於確定是否已針對請求為用戶授權。 如果請求未獲授權,授權篩選器可以讓管道短路。
-
- 授權后運行。
- OnResourceExecuting 可以在篩選器管道的其余階段之前運行代碼。 例如,
OnResourceExecuting
可以在模型綁定之前運行代碼。 - OnResourceExecuted 可以在管道的其余階段完成之后運行代碼。
-
操作篩選器可以在調用單個操作方法之前和之后立即運行代碼。 它們可用於處理傳入某個操作的參數以及從該操作返回的結果。 不可在 Razor Pages 中使用操作篩選器。
-
異常篩選器用於在向響應正文寫入任何內容之前,對未經處理的異常應用全局策略。
-
結果篩選器可以在執行單個操作結果之前和之后立即運行代碼。 僅當操作方法成功執行時,它們才會運行。 對於必須圍繞視圖或格式化程序的執行的邏輯,它們很有用。
下圖展示了篩選器類型在篩選器管道中的交互方式。
實現
通過不同的接口定義,篩選器同時支持同步和異步實現。
同步篩選器可以在其管道階段之前 (On-Stage-Executing
) 和之后 (On-Stage-Executed
) 運行代碼。 例如,OnActionExecuting
在調用操作方法之前調用。 OnActionExecuted
在操作方法返回之后調用。
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
方法:
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 類實現 IActionFilter
、IResultFilter
及其異步等效接口。
篩選器接口的同步和異步版本任意實現一個,而不是同時實現。 運行時會先查看篩選器是否實現了異步接口,如果是,則調用該接口。 如果不是,則調用同步接口的方法。 如果在一個類中同時實現異步和同步接口,則僅調用異步方法。 使用抽象類時(如 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", "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."); }
多種篩選器接口具有相應屬性,這些屬性可用作自定義實現的基類。
篩選器屬性:
- ActionFilterAttribute
- ExceptionFilterAttribute
- ResultFilterAttribute
- FormatFilterAttribute
- ServiceFilterAttribute
- TypeFilterAttribute
篩選器作用域和執行順序
可以將篩選器添加到管道中的三個作用域之一:
- 在操作上使用屬性。
- 在控制器上使用屬性。
- 所有控制器和操作的全局篩選器,如下面的代碼所示:
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 代碼。
- 控制器篩選器的 before 代碼。
- 全局篩選器的 after 代碼。
下面的示例闡釋了為同步操作篩選器調用篩選器方法的順序。
序列 | 篩選器作用域 | 篩選器方法 |
---|---|---|
1 | Global | OnActionExecuting |
2 | 控制器 | OnActionExecuting |
3 | 方法 | OnActionExecuting |
4 | 方法 | OnActionExecuted |
5 | 控制器 | OnActionExecuted |
6 | Global | OnActionExecuted |
此序列顯示:
- 方法篩選器已嵌套在控制器篩選器中。
- 控制器篩選器已嵌套在全局篩選器中。
控制器和 Razor 頁面級篩選器
繼承自 Controller 基類的每個控制器包括 Controller.OnActionExecuting、Controller.OnActionExecutionAsync 和 Controller.OnActionExecuted OnActionExecuted
方法。這些方法:
- 覆蓋為給定操作運行的篩選器。
OnActionExecuting
在所有操作篩選器之前調用。OnActionExecuted
在所有操作篩選器之后調用。OnActionExecutionAsync
在所有操作篩選器之前調用。next
之后的篩選器中的代碼在操作方法之后運行。
例如,在下載示例中,啟動時全局應用 MySampleActionFilter
。
TestController
:
- 將
SampleActionFilterAttribute
([SampleActionFilter]
) 應用於FilterTest2
操作。 - 重寫
OnActionExecuting
和OnActionExecuted
。
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
屬性:
[MyFilter(Name = "Controller Level Attribute", Order=1)]
請考慮前面示例中所示的 3 個相同操作篩選器。 如果控制器和全局篩選器的 Order
屬性分別設置為 1 和 2,則會反轉執行順序。
序列 | 篩選器作用域 | 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 屬性,可以使篩選器管道短路。 例如,以下資源篩選器將阻止執行管道的其余階段:
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
屬性。
[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
:
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 容器中:
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
篩選器的實例:
[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
將參數傳遞到類型:
[TypeFilter(typeof(LogConstantFilter),
Arguments = new object[] { "Method 'Hi' called" })]
public IActionResult Hi(string name) { return Content($"Hi {name}"); }
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) { } }
授權篩選器
授權篩選器:
- 是篩選器管道中運行的第一個篩選器。
- 控制對操作方法的訪問。
- 具有在它之前的執行的方法,但沒有之后執行的方法。
自定義授權篩選器需要自定義授權框架。 建議配置授權策略或編寫自定義授權策略,而不是編寫自定義篩選器。 內置授權篩選器:
- 調用授權系統。
- 不授權請求。
不會在授權篩選器中引發異常:
- 不會處理異常。
- 異常篩選器不會處理異常。
在授權篩選器出現異常時請小心應對。
詳細了解授權。
資源篩選器
資源篩選器:
- 實現 IResourceFilter 或 IAsyncResourceFilter 接口。
- 執行會覆蓋篩選器管道的絕大部分。
- 只有授權篩選器在資源篩選器之前運行。
如果要使大部分管道短路,資源篩選器會很有用。 例如,如果緩存命中,則緩存篩選器可以繞開管道的其余階段。
資源篩選器示例:
-
之前顯示的短路資源篩選器。
-
DisableFormValueModelBindingAttribute:
- 可以防止模型綁定訪問表單數據。
- 用於上傳大型文件,以防止表單數據被讀入內存。
操作篩選器
重要
操作篩選器不應用於 Razor Pages。 Razor Pages 支持 IPageFilter 和 IAsyncPageFilter。有關詳細信息,請參閱 Razor 頁面的篩選方法。
操作篩選器:
- 實現 IActionFilter 或 IAsyncActionFilter 接口。
- 它們的執行圍繞着操作方法的執行。
以下代碼顯示示例操作篩選器:
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
操作篩選器可用於:
- 驗證模型狀態。
- 如果狀態無效,則返回錯誤。
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
,從操作方法中將它正常返回。
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); } }
異常篩選器
異常篩選器:
- 實現 IExceptionFilter 或 IAsyncExceptionFilter。
- 可用於實現常見的錯誤處理策略。
下面的異常篩選器示例使用自定義錯誤視圖,顯示在開發應用時發生的異常的相關詳細信息:
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 標頭的結果篩選器:
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
:
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):
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
作為另一種創建篩選器的方法:
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) 中加載指定的類型。
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]
的三種方法:
[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
方法的類型,該方法可指定要注入到篩選器管道的中間件。 下面的示例使用本地化中間件為請求建立當前區域性:
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}"); }
中間件篩選器與資源篩選器在篩選器管道的相同階段運行,即,在模型綁定之前以及管道的其余階段之后。
后續操作
- 請參閱 Razor Pages 的篩選器方法。
- 若要嘗試使用篩選器,請下載、測試並修改 GitHub 示例。