ASP.NET MVC 過濾器 可在執行管道的前後特定階段執行代碼。過濾器可以配置為全局有效、僅對控制器有效或是僅對 Action 有效。 ...
原文:Filters
作者:Steve Smith
翻譯:劉怡(AlexLEWIS)
校對:何鎮汐
ASP.NET MVC 過濾器 可在執行管道的前後特定階段執行代碼。過濾器可以配置為全局有效、僅對控制器有效或是僅對 Action 有效。
過濾器如何工作?
不同的過濾器類型會在執行管道的不同階段運行,因此它們各自有一套適用場景。根據你實際要解決的問題以及在請求管道中執行的位置來選擇創建不同的過濾器。運行於 MVC Action 調用管道內的過濾器有時被稱為 過濾管道 ,當 MVC 選擇要執行哪個 Action 後就會先執行該 Action 上的過濾器。
不同過濾器在管道的不同位置運行。像授權這樣的過濾器只運行在管道的靠前位置,並且其後也不會跟隨 action。其它過濾器(如 action 過濾器等)可以在管道的其它部分之前或之後執行,如下所示。
選擇過濾器
授權過濾器 用於確定當前用戶的請求是否合法。
資源過濾器 是授權之後第一個用來處理請求的過濾器,也是最後一個接觸到請求的過濾器(因為之後就會離開過濾器管道)。在性能方面,資源過濾器在實現緩存或短路過濾器管道尤其有用。
Action 過濾器 包裝了對單個 action 方法的調用,可以將參數傳遞給 action 並從中獲得 action result。
異常過濾器 為 MVC 應用程式未處理異常應用全局策略。
結果過濾器 包裝了單個 action result 的執行,當且僅當 action 方法成功執行完畢後方纔運行。它們是理想的圍繞視圖執行或格式處理的邏輯(所在之處)。
實現
所有過濾器均可通過不同的介面定義來支持同步和非同步實現。根據你所需執行的任務的不同來選擇同步還是非同步實現。從框架的角度來講它們是可以互換的。
同步過濾器定義了 OnStageExecuting 方法和 OnStageExecuted 方法(當然也有例外)。OnStageExecuting 方法在具體事件管道階段之前調用,而 OnStageExecuted 方法則在之後調用(比如當 Stage 是 Action 時,這兩個方法便是 OnActionExecuting 和 OnActionExecuted,譯者註)。
using FiltersSample.Helper;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
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 方法,可以在具體管道階段的前後運行。On\ Stage\ ExecutionAsync 方法被提供了一個 Stage\ ExecutionDelegate 委托,當調用時該委托會執行具體管道階段的工作,然後等待其完成。
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
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
}
}
}
註解
只能 實現一個過濾器介面,要麼是同步版本的,要麼是非同步版本的,魚和熊掌不可兼得 。如果你需要在介面中執行非同步工作,那麼就去實現非同步介面。否則應該實現同步版本的介面。框架會首先檢查是不是實現了非同步介面,如果實現了非同步介面,那麼將調用它。不然則調用同步介面的方法。如果一個類中實現了兩個介面,那麼只有非同步方法會被調用。最後,不管 action 是同步的還是非同步的,過濾器的同步或是非同步是獨立於 action 的。
過濾器作用域
過濾器具有三種不同級別的 作用域 。你可以在特定的 action 上用特性(Attribute)的方式使用特定的過濾器;也可以在控制器上用特性的方式使用過濾器,這樣就會將效果應用在控制器內所有的 action 上;或者註冊一個全局過濾器,它將作用於整個 MVC 應用程式下的每一個 action。
如果想要使用全局過濾器的話,在你配置 MVC 的時候在 Startup
的 ConfigureServices
方法中添加:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(typeof(SampleActionFilter)); // by type
options.Filters.Add(new SampleGlobalActionFilter()); // an instance
});
services.AddScoped<AddHeaderFilterWithDi>();
}
過濾器可通過類型添加,也可以通過實例添加。如果通過實例添加,則該實例會被用於每一個請求。如果通過類型添加,則將會 type-activated(意思是說每次請求都會創建一個實例,其所有構造函數依賴項都將通過 DI 來填充)。通過類型添加過濾器相當於 filters.Add(new TypeFilterAttribute(typeof(MyFilter)))
。
把過濾器介面的實現當做 特性(Attributes) 使用是極為方便的。過濾器特性(filter attributes)可應用於控制器(Controllers)和 Action 方法。框架包含了內置的基於特性的過濾器,你可繼承它們或另外定製。比方說,下例過濾器繼承了 ResultFilterAttribute ,並重寫(override)了 OnResultExecuting
方法(在響應中增加了一個頭信息)。
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
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);
}
}
}
特性允許過濾器接收參數,如下例所示。可將此特性加諸控制器(Controller)或 Action 方法,併為其指定所需 HTTP 頭的名稱和值,並將該 HTTP 頭加入響應中:
[AddHeader("Author", "Steve Smith @ardalis")]
public class SampleController : Controller
{
public IActionResult Index()
{
return Content("Examine the headers using developer tools.");
}
}
Index
Action 的結果如下所示:響應的頭信息顯示在右下角。
以下幾種過濾器介面可以自定義為相應特性的實現。
過濾器特性:
- ActionFilterAttribute
- ExceptionFilterAttribute
- ResultFilterAttribute
- FormatFilterAttribute
- ServiceFilterAttribute
- TypeFilterAttribute
取消與短路
通過設置傳入過濾器方法的上下文參數中的 Result
屬性,可以在過濾器管道的任意一點短路管道。比方說,下麵的 ShortCircuitingResourceFilter
將阻止所有它之後管道內的所有過濾器,包括所有 action 過濾器。
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
public class ShortCircuitingResourceFilterAttribute : Attribute,
IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.Result = new ContentResult()
{
Content = "Resource unavailable - header should not be set"
};
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
}
在下例中,ShortCircuitingResourceFilter
和 AddHeader
過濾器都指向了名為 SomeResource
的 action 方法。然而,由於首先運行的是 ShortCircuitingResourceFilter
,它短路了剩下的管道,SomeResource
上的 AddHeader
過濾器不會運行。如果這兩個過濾器都以 Action 方法級別出現,它們的結果也會是一樣的(只要 ShortCircuitingResourceFilter
首先運行,請查看 Ordering )。
[AddHeader("Author", "Steve Smith @ardalis")]
public class SampleController : Controller
{
[ShortCircuitingResourceFilter]
public IActionResult SomeResource()
{
return Content("Successful access to resource - header should be set.");
}
配置過濾器
全局過濾器在 Startup.cs
中配置。基於特性的過濾器如果不需要任何依賴項的話,可以簡單地繼承一個與已存在的過濾器相對應的特性類型。如果要創建一個 非 全局作用域、但需要從依賴註入(DI)中獲得依賴項的過濾器,在它們上面加上 ServiceFilterAttribute
或 TypeFilterAttribute
特性,這樣就可用於控制器或 action 了。
依賴註入
以特性形式實現的、直接添加到控制器(Controller)類或 Action 方法的過濾器,其構造函數不得由 依賴註入 (DI)提供依賴項。其原因在於特性所需的構造函數參數必須由使用處直接提供。這是特性原型機理的限制。
不過,如果過濾器需要從 DI 中獲得依賴項,那麼有幾種辦法可以實現,可在類(class)或 Action 方法使用:
TypeFilter
將為其依賴項從 DI 中使用服務(services)來實例化一個實例。ServiceFilter
則從 DI 中取回一個過濾器實例。下例中將演示如何使用 ServiceFilter
:
[ServiceFilter(typeof(AddHeaderFilterWithDi))]
public IActionResult Index()
{
return View();
}
如果在 ConfigureServices
中直接使用未經註冊的 ServiceFilter
過濾器,則會拋出以下異常:
System.InvalidOperationException: No service for type
'FiltersSample.Filters.AddHeaderFilterWithDI' has been registered.
為避免此異常,你必須在 ConfigureServices
中為 AddHeaderFilterWithDI
類型註冊:
services.AddScoped<AddHeaderFilterWithDi>();
ServiceFilterAttribute
實現了 IFilterFactory
介面,後者暴露了創建 IFilter
實例的單一方法。在 ServiceFilterAttribute
中,介面 IFilterFactory
中定義的 CreateInstance
方法被實現為用於從服務容器(DI)載入指定類型。
TypeFilterAttribute
很像 ServiceFilterAttribute
(它同樣是 IFilterFactory
的實現),但此類型並非直接解析自 DI 容器。
相反,它通過使用 Microsoft.Extensions.DependencyInjection.ObjectFactory
來實例化類型。
由於這種不同,使用 TypeFilterAttribute
引用的類型不需要在使用之前向容器註冊(但它們依舊將由容器來填充其依賴項)。同樣地,TypeFilterAttribute
能可選地接受該類型的構造函數參數。下例演示如何向使用 TypeFilterAttribute
修飾的類型傳遞參數:
[TypeFilter(typeof(AddHeaderAttribute),
Arguments = new object[] { "Author", "Steve Smith (@ardalis)" })]
public IActionResult Hi(string name)
{
return Content($"Hi {name}");
}
若是你有一個簡單的不需要任何參數的、但其構造函數需要通過 DI 填充依賴項的過濾器的話,你可以通過繼承 TypeFilterAttribute
,在類(class)或方法(method)上使用自己命名的特性(來取代 [TypeFilter(typeof(FilterType))]
)。下例過濾器向你展示這是如何實現的:
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]
這樣的語法應用於類或方法,而不必使用 [TypeFilter]
或 [ServiceFilter]
。
註解
應避免純粹為記錄日誌而創建和使用過濾器,這是因為 內建的框架日誌功能 應該已經提供了你所需的功能。如果你要把日誌記錄功能放入過濾器中,它應專註於業務領域或過濾器的具體行為,而不是 MVC Action 或框架事件。
IFilterFactory
實現了 IFilter
。因此,在過濾器管道的任何地方 IFilterFactory
實例都可當做 IFilter
實例來使用。當框架準備調用過濾器,將試圖把其強制轉換為 IFilterFactory
。如果轉換成功,將通過調用 CreateInstance
方法創建即將被調用的 IFilter
實例。因為過濾器管道不需要在應用程式啟動時顯式設置了,所以這是一種非常靈活的設計。
你可以在自己的特性實現中實現 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[] { "Header Added" });
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
}
排序
過濾器可應用於 Action 方法、控制器(Controller,通過特性(attribute)的形式)或添加到全局過濾器集合中。其作用域通常還決定了其執行順序。最靠近 Action 的過濾器首先執行;通常來講通過重寫行為而不是顯式設置順序來改變順序。這有時被稱為“俄羅斯套娃”,因為每一個作用範圍都包裹了前一個作用範圍,就像是 套娃 那般。
除了作用範圍,過濾器還可以通過實現 IOrderedFilter 來重寫它們的執行順序。該介面只是簡單地暴露了 int
Order
屬性,然後執行時根據該數字正排序(數字越小,越先執行)後依次執行過濾器。所有內建過濾器(包括 TypeFilterAttribute
和 ServiceFilterAttribute
)都實現了 IOrderedFilter
介面,因此當你將過濾器以特性的方式用於類(class)或方法(method)時你可以指定每一個過濾器的執行順序。預設情況下所有內建過濾器的 Order
屬性值都為 0,因此作用範圍就當做決定性的因素(除非存在不為 0 的 Order
值)。
每個繼承自 Controller
基類的控制器(Controller)都包含 OnActionExecuting
和 OnActionExecuted
方法。這些方法為給定的 Action 包裝了過濾器,它們分別在最先和最後運行。基於作用範圍的順序(假設沒有為過濾器的 Order
設置任何值):
- The Controller OnActionExecuting
- The Global filter OnActionExecuting
- The Class filter OnActionExecuting
- The Method filter OnActionExecuting
- The Method filter OnActionExecuted
- The Class filter OnActionExecuted
- The Global filter OnActionExecuted
- The Controller OnActionExecuted
註解
當過濾器將啟動運行、需要決定過濾器執行順序時,IOrderedFilter
會向外宣佈自己的作用範圍。過濾器首先通過 order 來排序,然後通過作用範圍來決定。如果不設置,則 Order 預設為 0。
為在基於作用範圍的排序中修改預設值,你須在類一級(class-level)或方法一級(method-level)的過濾器上顯式設置 Order
屬性。比如為方法一級的特性增加 Order=-1
:
[MyFilter(Name = "Method Level Attribute", Order=-1)]
這種情況下,小於 0 的值將確保該過濾器在全局過濾器和類一級過濾器之前運行(假設它們的 Order
屬性均未設置)。
新的排序可能是這樣的:
- The Controller OnActionExecuting
- The Method filter OnActionExecuting
- The Global filter OnActionExecuting
- The Class filter OnActionExecuting
- The Class filter OnActionExecuted
- The Global filter OnActionExecuted
- The Method filter OnActionExecuted
- The Controller OnActionExecuted
註解
Controller
類的方法總是在所有過濾器之前和之後運行。這些方法並未實現為IFilter
實現,同時它們不參與IFilter
的排序演算法。
授權過濾器
授權過濾器 控制對 action 方法的訪問,也是過濾器管道中第一個被執行的過濾器。它們只有一個前置階段,不像其它大多數過濾器支持前置階段方法和後置階段方法。只有當你使用自己的授權框架時才需要定製授權過濾器。謹記勿在授權過濾器內拋出異常,這是因為所拋出的異常不會被處理(異常過濾器也不會處理它們)。此時記錄該問題或尋求其它辦法。
更多請訪問 Authorization
資源過濾器
資源過濾器 要麼實現 IResourceFilter
介面,要麼實現 IAsyncResourceFilter
介面,它們執行於大多數過濾器管道(只有 授權過濾器 在其之前運行,其餘所有過濾器以及 Action 處理均出現在其 OnResourceExecuting
和 OnResourceExecuted
方法之間)。當你需要短路絕大多數正在進行的請求時,資源過濾器特別有用。資源過濾器的一個典型例子是緩存,如果響應已經被緩存,過濾器會立即將之置為結果以避免後續 Action 的多餘操作過程。
上面所說的是一個 短路資源過濾器 的例子。下例是一個非常簡單的緩存實現(請勿將之用於生產環境),只能與 ContentResult
配合使用,如下所示:
public class NaiveCacheResourceFilterAttribute : Attribute,
IResourceFilter
{
private static readonly Dictionary<string, object> _cache
= new Dictionary<string, object>();
private string _cacheKey;
public void OnResourceExecuting(ResourceExecutingContext context)
{
_cacheKey = context.HttpContext.Request.Path.ToString();
if (_cache.ContainsKey(_cacheKey))
{
var cachedValue = _cache[_cacheKey] as string;
if (cachedValue != null)
{
context.Result = new ContentResult()
{ Content = cachedValue };
}
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
if (!String.IsNullOrEmpty(_cacheKey) &&
!_cache.ContainsKey(_cacheKey))
{
var result = context.Result as ContentResult;
if (result != null)
{
_cache.Add(_cacheKey, result.Content);
}
}
}
}
在 OnResourceExecuting
中,如果結果已經在靜態欄位緩存中,Result
屬性將被設置到 context
上,同時 Action 被短路並返回緩存的結果。在 OnResourceExecuted
方法中,如果當前其請求的鍵未被使用過,那麼 Result
就會被保存到緩存中,用於之後的請求。
如下所示,把這個過濾器用於類或方法之上:
[TypeFilter(typeof(NaiveCacheResourceFilterAttribute))]
public class CachedController : Controller
{
public IActionResult Index()
{
return Content("This content was generated at " + DateTime.Now);
}
}
Action 過濾器
Action 過濾器 要麼實現 IActionFilter
介面,要麼實現 IAsyncActionFilter
介面,它們可以在 action 方法執行的前後被執行。Action 過濾器非常適合放置諸如查看模型綁定結果、或是修改控制器或輸入到 action 方法的邏輯。另外,action 過濾器可以查看並直接修改 action 方法的結果。
OnActionExecuting
方法在 action 方法執行之前運行,因此它可以通過改變 ActionExecutingContext.ActionArguments
來控制 action 的輸入,或是通過 ActionExecutingContext.Controller
控制控制器(Controller)。OnActionExecuting
方法可以通過設置 ActionExecutingContext.Result
來短路 action 方法的操作及其後續的過濾器。OnActionExecuting
方法通過拋出異常也可以阻止 action 方法和後續過濾器的處理,但會當做失敗(而不是成功)的結果來處理。
OnActionExecuted
方法在 action 方法執行之後才執行,並且可以通過 ActionExecutedContext.Result
屬性查看或控制 action 的結果。如果 action 在執行時被其它過濾器短路,則 ActionExecutedContext.Canceled
將會被置為 true。如果 action 或後續的 action 過濾器拋出異常,則 ActionExecutedContext.Exception
會被設置為一個非空值。有效「處理」完異常後把 ActionExecutedContext.Exception
設置為 null,那麼 ActionExectedContext.Result
會像從 action 方法正常返回值那樣被處理。
對於 IAsyncActionFilter
介面來說,它的 OnActionExecutionAsync
方法結合了 OnActionExecuting
和 OnActionExecuted
的所有能力。調用 await next()
後,ActionExecutionDelegate
將會執行所有的後續 action 過濾器以及 action 方法,並返回 ActionExecutedContext
。
如果想要在 OnActionExecutionAsync
內部短路,那麼就為 ActionExecutingContext.Result
分配一個結果實例,並且不要調用 ActionExecutionDelegate
即可。
異常過濾器
異常過濾器 實現了 IExceptionFilter
介面或 IAsyncExceptionFilter
介面。
異常過濾器用於處理「未處理異常」,包括發生在 Controller 創建及 模型綁定 期間出現的異常。它們只在管道內發生異常時才會被調用。它們提供了一個單一的位置實現應用程式內的公共異常處理策略。框架提供了抽象的 ExceptionFilterAttribute ,你根據自己的需要繼承這個類。異常過濾器適用於捕獲 MVC Action 內出現的異常,但它們不及錯誤處理中間件(error handling middleware)靈活。一般來講優先使用中間件,只有在需要做一些基於所選 MVC Action 的、有別於錯誤處理的工作時才選擇使用過濾器。
提示
對於應用程式中不同 action 需要使用不同的錯誤處理方式,並向 Views/HTML 暴露 API 端點或 action 的錯誤處理結果。API 端點用 JSON 返回錯誤信息,而基於視圖的 action 則返回錯誤頁面(HTML 頁面)。
異常過濾器不應有兩個事件(對於前置或後置而言),它們只實現 OnException
(或 OnExceptionAsync
)。以參數形式傳入 OnException
的 ExceptionContext
包含了所發生的 Exception
。如果把 context.Exception
設置為 null,其效果相當於你已處理該異常,所以該次請求會像沒發生過異常那樣繼續處理(一般會返回 HTTP 200 OK 狀態)。下例過濾器中使用定製的開發者錯誤視圖來顯示開發環境中應用程式所出現異常的詳細信息:
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace FiltersSample.Filters
{
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IModelMetadataProvider _modelMetadataProvider;
public CustomExceptionFilterAttribute(
IHostingEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
_modelMetadataProvider = modelMetadataProvider;
}
public override void OnException(ExceptionContext context)
{
if (!_hostingEnvironment.IsDevelopment())
{
// do nothing
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.ExceptionHandled = true; // mark exception as handled
context.Result = result;
}
}
}
結果過濾器
實現了 IResultFilter
或 IAsyncResultFilter
介面的 結果過濾器 在 Action Result 執行體的周圍執行。當 Action 或 Action 過濾器產生 Action 結果時,只有成功運行的才會執行結果過濾器。如果異常過濾器處理了異常,那麼結果過濾器就不會運行——除非異常過濾器將異常設置為null(Exception = null
)。
註解
正在執行的結果種類取決於相關 Action。MVC Action 所返回的 View 將包含 Razor(將其作為正在處理的ViewResult
的一部分)。API 方法則將執行一些序列化工作作為其執行結果的一部分。瞭解更多請移步 action 結果。
結果過濾器適用於任何需要直接環繞 View 或格式化處理的邏輯。結果過濾器可以替換或更改 Action 結果(而後者負責產生響應)。
OnResultExecuting
方法運行於 Action 結果執行之前,故其可通過 ResultExecutingContext.Result
操作 Action 結果。如果將 ResultExecutingContext.Cancel
設置為 true,則 OnResultExecuting
方法可短路 Action 結果以及後續結果過濾器的執行。如果發生了短路,MVC 將不會修改響應,所以當發生短路時,為避免生成空響應,你一般應該直接去修改響應對象。如果在 OnResultExecuting
方法內拋出異常,那麼也將阻止 Action 結果以及後續過濾器的執行,但會被當做失敗結果(而非成功結果)。
OnResultExecuted
方法運行於 Action 結果執行之後。也就是說,如果沒有拋出異常,響應可能就會被髮送到客戶端且不可再修改。如果 Action 結果在執行中被其它過濾器短路,則 ResultExecutedContext.Canceled
將被置為 true。如果 Action 結果或後續結果過濾器拋出異常,則 ResultExecutedContext.Exception
將被置為非空值(non-null value)。把 ResultExecutedContext.Exception
設置為 null 後會影響到異常的“處理”,這將阻止異常在之後的管道內被 MVC 重新拋出。如果在結果過濾器內處理異常,需要確定此處是否適合將某些數據寫入響應中。如果 Action 結果在執行中途拋出異常,而 header 也已被更新到客戶端,那麼將沒有任何可靠的機制來發送失敗代碼。
對於 IAsyncResultFilter
的 OnResultExecutionAsync
方法來講,它具有 OnResultExecuting
和 OnResultExecuted
的功能。在 ResultExecutionDelegate
上調用 await next()
將執行後續的結果過濾器和 Action 結果,並返回 ResultExecutedContext
。如果將 ResultExecutingContext.Cancel
值為 true 並不調用 ResultExectionDelegate
,則將在內部短路 OnResultExecutionAsync
。
你可以覆蓋內建的 ResultFilterAttribute
特性,創建定製的結果過濾器, AddHeaderAttribute
類便是一例結果過濾器。
提示
若你需要為響應增加 header,在 Action 結果執行前如是做。否則響應就會被髮送到客戶端,屆時改之晚矣。故對於結果過濾器而言,為響應增加 header 需要在OnResultExecuting
中處理(而不是在OnResultExecuted
中)。
過濾器對比中間件
一般情況下,過濾器用於處理業務與應用程式的橫切關註點。它的用法很像 中間件 。從能力上來講過濾器酷似中間件,但過濾器的作用範圍很大,因此允許你將它插入到應用程式中需要使用到它的場合中,比如在視圖之前或在模型綁定之後。過濾器是 MVC 的一部分,可以訪問 MVC 的上下文以及構造函數。比方說,中間件不能簡單地直接察覺請求中模型驗證是否生成了錯誤並對此作出響應,而過濾器卻能做到。
如果想要嘗試一下過濾器,可以下載、測試並修改樣例 。