注:本文隸屬於《理解ASP.NET Core》系列文章,請查看置頂博客或點擊此處查看全文目錄
Filter概覽
如果你是從ASP.NET一路走過來的,那么你一定對過濾器(Filter)不陌生。當然,ASP.NET Core仍然繼承了過濾器機制。
過濾器運行在過濾器管道中,這是一張官方的圖,很好地解釋了過濾器管道在HTTP請求管道中的位置:

可以看到,只有當路由選擇了MVC Action之后,過濾器管道才有機會執行。
過濾器不止一種,而是有多種類型。為了讓各位對各過濾器執行順序更容易理解一下,我把官方的圖魔改了一下,如下:

Authorization Filters
(授權過濾器):該過濾器位於所有過濾器的頂端,首先被執行。授權過濾器用於確認請求用戶是否已授權,如未授權,則可以將管道短路,禁止請求繼續傳遞。Resource Filters
(資源過濾器):當授權通過后,可以在過濾器管道的其他階段(如模型綁定)之前和之后執行自定義邏輯Action Filters
(操作過濾器):在調用Action之前和之后執行自定義邏輯。通過操作過濾器,可以修改要傳入Action的參數,也可以設置或修改Action的返回結果。另外,其也可以首先捕獲到Action中拋出的未處理異常並進行處理。Exception Filters
(異常過濾器):當Controller創建時、模型綁定、Action Filters和Action執行中拋出未處理的異常時,異常過濾器可以捕獲並進行處理,需要注意的是,在此之前,響應正文還未被寫入,這意味着你可以設置返回結果。Result Filters
(結果過濾器):僅當Action的執行未拋出異常,或Action Filter處理了異常時,才會執行結果過濾器,允許你在操作結果執行之前和之后執行自定義邏輯。
東西有點多,你要忍一下。等看過下面的詳細介紹之后,再來回顧上面,就很容易理解了。
這些過濾器,均實現了
IFilterMetadata
接口,該接口不包含任何行為,僅僅是用於標記這是MVC請求管道中的過濾器。
另外,如Resource Filters、Action Filters和Result Filters這種,他們擁有兩個行為,分別在管道階段的之前和之后執行,並按照習慣,將之前命名為OnXXXing
,如 OnActionExecuting,將之后命名為OnXXXExecuted
,如 OnActionExecuted
過濾器的作用域和注冊方式
由於過濾器的種類繁多,為了方便大家邊學習邊測試,所以先介紹一下過濾器的作用域和注冊方式。
過濾器的作用域范圍和執行順序
同樣的,在介紹過濾器之前,先給大家介紹一下過濾器的作用域范圍和執行順序。
過濾器的作用域范圍,可分為三種,從小到大是:
- 某個Controller中的某個Action上(不支持Razor Page中的處理方法)
- 某個Controller或Razor Page上
- 全局,應用到所有Controller、Action和Razor Page上
不同過濾器的執行順序,我們通過上面那幅圖可以很清楚的知曉了,但是對於不同作用域的同一類型的過濾器,執行順序又是怎樣的呢?
以IActionFilter
舉例說明,執行順序為:
- 全局過濾器的 OnActionExecuting
- Controller和Razor Page過濾器的 OnActionExecuting
- Action過濾器的 OnActionExecuting
- Action過濾器的 OnActionExecuted
- Controller和Razor Page過濾器的 OnActionExecuted
- Controller和Razor Page過濾器的 OnActionExecuting
- 全局過濾器的 OnActionExecuted
也就是說,對於不同作用域的同一類型的過濾器,執行順序是由作用域范圍大到小,然后再由小到大
過濾器的注冊方式
接下來,看一下如何將過濾器注冊為不同的作用域:
全局
注冊為全局比較簡單,直接配置MvcOptions.Filters
即可:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => options.Filters.Add<MyFilter>());
// or
services.AddControllers(options => options.Filters.Add<MyFilter>());
// or
services.AddControllersWithViews(options => options.Filters.Add<MyFilter>());
}
Controller、Razor Page 或 Action
作用域為 Controller、Razor Page 或 Action 在注冊方式上來說,實際上都是差不多的,都是以特性的方式進行標注。
最簡單的,過濾器構造函數無參數或這些參數均無需由DI來提供,此時只需要過濾器繼承Attribute
即可:
class MyFilterAttribute : Attribute, IActionFilter { }
[MyFilter]
public class HomeController : Controller { }
另一種,過濾器的構造函數參數均需要DI來提供,此時就需要用到ServiceFilterAttribute
了:
class MyFilter :IActionFilter
{
public MyFilter(IWebHostEnvironment env) { }
}
public void ConfigureServices(IServiceCollection services)
{
// 將過濾器添加到 DI 容器
services.AddScoped<MyFilter>();
}
[ServiceFilter(typeof(MyFilter))]
public class HomeController : Controller { }
那ServiceFilterAttribute
是如何創建這種類型過濾器的實例的呢?看下它的結構你就明白了:
public interface IFilterFactory : IFilterMetadata
{
// 過濾器實例是否可跨請求重用
bool IsReusable { get; }
// 通過 IServiceProvider 創建指定過濾器類型的實例
IFilterMetadata CreateInstance(IServiceProvider serviceProvider);
}
public class ServiceFilterAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter
{
// type 就是要創建的過濾器的類型
public ServiceFilterAttribute(Type type)
{
ServiceType = type ?? throw new ArgumentNullException(nameof(type));
}
public int Order { get; set; }
// 獲取過濾器的類型,也就是構造函數中傳進來的
public Type ServiceType { get; }
// 過濾器實例是否可跨請求重用,默認 false
public bool IsReusable { get; set; }
// 通過 IServiceProvider.GetRequiredService 創建指定過濾器類型的實例
// 所以要求該過濾器和構造函數參數要在DI容器中注冊
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var filter = (IFilterMetadata)serviceProvider.GetRequiredService(ServiceType);
if (filter is IFilterFactory filterFactory)
{
// 展開 IFilterFactory
filter = filterFactory.CreateInstance(serviceProvider);
}
return filter;
}
}
如果你想要使過濾器實例在其作用域之外被重用,可以通過指定IsReusable = true
來達到目的,需要注意的是要保證該過濾器所依賴的服務生命周期一定是單例的。另外,這並不能保證該過濾器實例是單例,也有可能出現多個。
好了,還有最后一種最復雜的,就是過濾器的構造函數部分不需要DI來提供,部分又需要DI來提供,此時就需要用到TypeFilterAttribute
了:
class MyFilter : IActionFilter
{
// 第一個參數 caller 不是通過DI提供的
// 第二個參數 env 是通過DI提供的
public MyFilter(string caller, IWebHostEnvironment env) { }
}
// ... 注意,這里就不需要將 MyFilter 注冊到DI容器了,記得將注冊代碼刪除
// Arguments 里面存放的參數就是無需DI提供的參數
[TypeFilter(typeof(MyFilter),
Arguments = new object[] { "HomeController" })]
public class HomeController : Controller { }
同樣,看一下TypeFilterAttribute
的結構:
public class TypeFilterAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter
{
private ObjectFactory _factory;
// type 就是要創建的過濾器的類型
public TypeFilterAttribute(Type type)
{
ImplementationType = type ?? throw new ArgumentNullException(nameof(type));
}
// 要傳遞給過濾器構造函數的非DI容器提供的參數
public object[] Arguments { get; set; }
// 獲取過濾器的類型,也就是構造函數中傳進來的
public Type ImplementationType { get; }
public int Order { get; set; }
public bool IsReusable { get; set; }
// 通過 ObjectFactory 創建指定過濾器類型的實例
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
if (_factory == null)
{
var argumentTypes = Arguments?.Select(a => a.GetType())?.ToArray();
_factory = ActivatorUtilities.CreateFactory(ImplementationType, argumentTypes ?? Type.EmptyTypes);
}
var filter = (IFilterMetadata)_factory(serviceProvider, Arguments);
if (filter is IFilterFactory filterFactory)
{
// 展開 IFilterFactory
filter = filterFactory.CreateInstance(serviceProvider);
}
return filter;
}
}
過濾器上下文
過濾器中的行為,都會有一個上下文參數,這些上下文參數都繼承自抽象類FilterContext
,而FilterContext
又繼承自ActionContext
(這也從側面說明了,過濾器就是為Action服務的):
public class ActionContext
{
// Action相關的信息
public ActionDescriptor ActionDescriptor { get; set; }
// HTTP上下文
public HttpContext HttpContext { get; set; }
// 模型綁定和驗證
public ModelStateDictionary ModelState { get; }
// 路由數據
public RouteData RouteData { get; set; }
}
public abstract class FilterContext : ActionContext
{
public virtual IList<IFilterMetadata> Filters { get; }
public bool IsEffectivePolicy<TMetadata>(TMetadata policy) where TMetadata : IFilterMetadata {}
public TMetadata FindEffectivePolicy<TMetadata>() where TMetadata : IFilterMetadata {}
}
當我們自定義一個過濾器時,免不了要和上下文進行交互,所以,了解上下文的結構,是不可或缺的。下面就挑兩個重要的參數探究一下。
我們先來看ActionDescriptor
,它里面包含了和Action相關的信息:
public class ActionDescriptor
{
// 標識該Action的唯一標識,其實就是一個Guid
public string Id { get; }
// 路由字典,包含了controller、action的名字等
public IDictionary<string, string> RouteValues { get; set; }
// 特性路由的相關信息
public AttributeRouteInfo? AttributeRouteInfo { get; set; }
// Action的約束列表
public IList<IActionConstraintMetadata>? ActionConstraints { get; set; }
// 終結點元數據,咱們一般用不到
public IList<object> EndpointMetadata { get; set; }
// 路由中的參數列表,包含參數名、參數類型、綁定信息等
public IList<ParameterDescriptor> Parameters { get; set; }
public IList<ParameterDescriptor> BoundProperties { get; set; }
// 過濾器管道中與當前Action有關的過濾器列表
public IList<FilterDescriptor> FilterDescriptors { get; set; }
// Action的個性化名稱
public virtual string? DisplayName { get; set; }
// 共享元數據
public IDictionary<object, object> Properties { get; set; }
}
下面的HttpContext
這個就不說了,太大了。不過你得知道,有了它,你可以針對請求和響應做自己想做的操作。
接下來就是ModelState
,它是用於校驗模型綁定的,通過它,可以知道模型是否綁定成功,也可以得到綁定失敗的校驗信息。相關細節將在后續關於模型綁定的文章中進行介紹。
然后就是RouteData
,很顯然,它存儲了和路由有關的信息,那就看一下它包括什么吧:
public class RouteData
{
// 當前路由路徑上由路由生成的數據標記
public RouteValueDictionary DataTokens { get; }
// Microsoft.AspNetCore.Routing.IRouter 的實例列表
public IList<IRouter> Routers { get; }
// 路由值,包含了 ActionDescriptor.RouteValues 中的數據
public RouteValueDictionary Values { get; }
}
后面,就來到了Filters
,看到IFilterMetadata
我相信你也已經猜到了,它表示過濾器管道中與當前Action有關的過濾器列表。
Authorization Filters
授權過濾器是過濾器管道的第一個被執行的過濾器,用於系統授權。一般不會編寫自定義的授權過濾器,而是配置授權策略或編寫自定義授權策略。詳細內容將在后續文章介紹。
Resource Filters
資源過濾器,在授權過濾器執行后執行,該過濾器包含“之前”和“之后”兩個行為,包裹了模型綁定、操作過濾器、Action執行、異常過濾器、結果過濾器以及結果執行。
通過實現IResourceFilter
或IAsyncResourceFilter
接口:
public interface IResourceFilter : IFilterMetadata
{
void OnResourceExecuting(ResourceExecutingContext context);
void OnResourceExecuted(ResourceExecutedContext context);
}
public interface IAsyncResourceFilter : IFilterMetadata
{
Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next);
}
當攔截到請求時,你可以得到資源信息的上下文:
public class ResourceExecutingContext : FilterContext
{
// 獲取或設置該Action的執行結果
public virtual IActionResult? Result { get; set; }
// Action參數綁定源提供器工廠,比如 Form、Route、QueryString、JQueryForm、FormFile等
public IList<IValueProviderFactory> ValueProviderFactories { get; }
}
public class ResourceExecutedContext : FilterContext
{
// 指示Action的執行是否已取消
public virtual bool Canceled { get; set; }
// 如果捕獲到未處理的異常,會存放到此處
public virtual Exception? Exception { get; set; }
public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; }
// 指示異常是否已被處理
public virtual bool ExceptionHandled { get; set; }
// 獲取或設置該Action的執行結果
public virtual IActionResult? Result { get; set; }
}
類似的,一旦設置了
Result
,就可以使過濾器管道短路。
對於ResourceExecutedContext
,有兩種方式來處理異常:
- 將
Exception
或ExceptionDispatchInfo
置為null
- 將
ExceptionHandled
置為true
單純的僅設置Result
是行不通的。所以我建議大家,在處理異常時,除了設置Result
外,也將ExceptionHandled
設置為true
,這樣也讓讀代碼的人更容易理解代碼邏輯。
另外,ResourceExecutedContext.Canceled
,用於指示Action的執行是否已取消。當在 OnResourceExecuting 中手動設置 ResourceExecutingContext.Result 時,會將 Canceled 置為 true。需要注意的是,想要測試這種情況,至少要注冊兩個資源過濾器,並且在第二個資源過濾器中設置Result,才能夠在第一個過濾器中看到效果。
Action Filters
操作過濾器,在模型綁定后執行,該過濾器同樣包含“之前”和“之后”兩個行為,包裹了Action的執行(不包含Controller的創建)。
如果Action執行過程中或后續操作過濾器中拋出異常,首先捕獲到異常的是操作過濾器的OnActionExecuted
,而不是異常過濾器。
通過實現IActionFilter
或IAsyncActionFilter
接口:
public interface IActionFilter : IFilterMetadata
{
void OnActionExecuting(ActionExecutingContext context);
void OnActionExecuted(ActionExecutedContext context);
}
public interface IAsyncActionFilter : IFilterMetadata
{
Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next);
}
同樣地,看一下上下文結構:
public class ActionExecutingContext : FilterContext
{
// 獲取或設置該Action的執行結果
public virtual IActionResult? Result { get; set; }
// Action的參數字典,key是參數名,value是參數值
public virtual IDictionary<string, object> ActionArguments { get; }
// 獲取該Action所屬的Controller
public virtual object Controller { get; }
}
public class ActionExecutedContext : FilterContext
{
// 指示Action的執行是否已取消
public virtual bool Canceled { get; set; }
// 獲取該Action所屬的Controller
public virtual object Controller { get; }
// 如果捕獲到未處理的異常,會存放到此處
public virtual Exception? Exception { get; set; }
public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; }
// 指示異常是否已被處理
public virtual bool ExceptionHandled { get; set; }
// 獲取或設置該Action的執行結果
public virtual IActionResult Result { get; set; }
}
關於ActionExecutedContext.Canceled
屬性和異常處理相關的知識點,均與資源過濾器類似,這里就不再贅述了。
由於操作過濾器常常在應用中的使用比較頻繁,所以這里詳細介紹一下它的使用。ASP.NET Core框架提供了一個抽象類ActionFilterAttribute
,該抽象類實現了多個接口,還繼承了Attribute
,允許我們以特性的方式使用。所以,一般比較建議大家通過繼承該抽象類來自定義操作過濾器:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class ActionFilterAttribute :
Attribute, IActionFilter, IAsyncActionFilter, IResultFilter, IAsyncResultFilter, IOrderedFilter
{
public int Order { get; set; }
public virtual void OnActionExecuting(ActionExecutingContext context) { }
public virtual void OnActionExecuted(ActionExecutedContext context) { }
public virtual async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
// 刪除了一些空校驗代碼...
OnActionExecuting(context);
if (context.Result == null)
{
OnActionExecuted(await next());
}
}
public virtual void OnResultExecuting(ResultExecutingContext context) { }
public virtual void OnResultExecuted(ResultExecutedContext context) { }
public virtual async Task OnResultExecutionAsync(
ResultExecutingContext context,
ResultExecutionDelegate next)
{
// 刪除了一些空校驗代碼...
OnResultExecuting(context);
if (!context.Cancel)
{
OnResultExecuted(await next());
}
}
}
可以看到,ActionFilterAttribute
同時實現了同步和異步接口,不過,我們在使用時,只需要實現同步或異步接口就可以了,不要同時實現。這是因為,運行時會先檢查過濾器是否實現了異步接口,如果是,則調用該異步接口。否則,就調用同步接口。 如果在一個類中同時實現了異步和同步接口,則僅會調用異步接口。
當要全局進行驗證模型綁定狀態時,使用操作過濾器再合適不過了!
public class ModelStateValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
if (context.HttpContext.Request.AcceptJson())
{
var errorMsg = string.Join(Environment.NewLine, context.ModelState.Values.SelectMany(v => v.Errors.Select(e => e.ErrorMessage)));
context.Result = new BadRequestObjectResult(AjaxResponse.Failed(errorMsg));
}
else
{
context.Result = new ViewResult();
}
}
}
}
public static class HttpRequestExtensions
{
public static bool AcceptJson(this HttpRequest request)
{
if (request == null) throw new ArgumentNullException(nameof(request));
var regex = new Regex(@"^(\*|application)/(\*|json)$");
return request.Headers[HeaderNames.Accept].ToString()
.Split(',')
.Any(type => regex.IsMatch(type));
}
}
Exception Filters
異常過濾器,可以捕獲Controller創建時(也就是只捕獲構造函數中拋出的異常)、模型綁定、Action Filter和Action中拋出的未處理異常。
再着重說明一下:如果Action執行過程中或非首個操作過濾器中拋出異常,首先捕獲到異常的是操作過濾器的OnActionExecuted
,而不是異常過濾器。但是,如果在Controller創建時拋出異常,那首先捕獲到異常的就是異常過濾器了。
我知道大家在初時異常過濾器的時候,有的人會誤認為它可以捕獲程序中的任何異常,這是不對的!
異常過濾器:
- 通過實現接口
IExceptionFilter
或IAsyncExceptionFilter
來自定義異常過濾器 - 可以捕獲Controller創建時(也就是只捕獲構造函數中拋出的異常)、模型綁定、Action Filter和Action中拋出的未處理異常
- 其他地方拋出的異常不會捕獲
先來看一下這兩個接口:
// 僅具有標記作用,標記其為 mvc 請求管道的過濾器
public interface IFilterMetadata { }
public interface IExceptionFilter : IFilterMetadata
{
// 當拋出異常時,該方法會捕獲
void OnException(ExceptionContext context);
}
public interface IAsyncExceptionFilter : IFilterMetadata
{
// 當拋出異常時,該方法會捕獲
Task OnExceptionAsync(ExceptionContext context);
}
OnException
和OnExceptionAsync
方法都包含一個類型為ExceptionContext
參數,很顯然,它就是與異常有關的上下文,我們的異常處理邏輯離不開它。那接着來看一下它的結構吧:
public class ExceptionContext : FilterContext
{
// 捕獲到的未處理異常
public virtual Exception Exception { get; set; }
public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; }
// 指示異常是否已被處理
// true:表示異常已被處理,異常不會再向上拋出
// false:表示異常未被處理,異常仍會繼續向上拋出
public virtual bool ExceptionHandled { get; set; }
// 設置響應的 IActionResult
// 如果設置了結果,也表示異常已被處理,異常不會再向上拋出
public virtual IActionResult? Result { get; set; }
}
下面,我們就來實現一個自定義的異常處理器:
public class MyExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IModelMetadataProvider _modelMetadataProvider;
public MyExceptionFilterAttribute(IModelMetadataProvider modelMetadataProvider)
{
_modelMetadataProvider = modelMetadataProvider;
}
public override void OnException(ExceptionContext context)
{
if (!context.ExceptionHandled)
{
// 此處僅為簡單演示
var exception = context.Exception;
var result = new ViewResult()
{
ViewName = "Error",
ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState)
{
// 記得給 ErrorViewModel 加上 Message 屬性
Model = new ErrorViewModel
{
Message = exception.ToString()
}
}
};
context.Result = result;
// 標記異常已處理
context.ExceptionHandled = true;
}
}
}
接着,找到/Views/Shared/Error.cshtml
,展示一下錯誤消息:
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<p>@Model.Message</p>
最后,注冊一下MyExceptionFilterAttribute
:
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<MyExceptionFilterAttribute>();
services.AddControllersWithViews();
}
現在,我們將該異常處理器加在/Home/Index
上,並拋個異常:
public class HomeController : Controller
{
[ServiceFilter(typeof(MyExceptionFilterAttribute))]
public IActionResult Index()
{
throw new Exception("Home Index Error");
return View();
}
}
當請求/Home/Index
時,你會得到如下頁面:
Result Filters
結果過濾器,包裹了操作結果的執行。所謂操作結果的執行,可以是Razor視圖的處理操作,也可以是Json結果的序列化操作等。
通過實現IResultFilter
或IAsyncResultFilter
接口:
public interface IResultFilter : IFilterMetadata
{
void OnResultExecuting(ResultExecutingContext context);
void OnResultExecuted(ResultExecutedContext context);
}
public interface IAsyncResultFilter : IFilterMetadata
{
Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next);
}
當實現這兩個接口其一時,則僅當Action或Action Filters生成Result時,才會執行結果過濾器。像授權、資源過濾器使管道短路或異常過濾器通過生成Result來處理異常等,都不會執行結果過濾器。
如果在 OnResultExecuting 中拋異常了,就會導致短路,Action結果和后續的結果過濾器都不會執行,並且執行結果也被視為失敗。
同樣地,看一下上下文結構:
public class ResultExecutingContext : FilterContext
{
// 獲取該Action所屬的Controller
public virtual object Controller { get; }
// 獲取或設置該Action的結果
public virtual IActionResult Result { get; set; }
// 指示結果過濾器是否應該被短路,若短路,Action結果和后續的的結果過濾器,都不會執行
public virtual bool Cancel { get; set; }
}
public class ResultExecutedContext : FilterContext
{
// 指示結果過濾器是否被短路,若短路,Action結果和后續的的結果過濾器,都不會執行
public virtual bool Canceled { get; set; }
// 獲取該Action所屬的Controller
public virtual object Controller { get; }
// 獲取或設置結果或結果過濾器執行過程中拋出的未處理異常
public virtual Exception? Exception { get; set; }
public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; }
// 異常是否已被處理
public virtual bool ExceptionHandled { get; set; }
// 獲取或設置該Action的執行結果
public virtual IActionResult Result { get; }
}
可以通過繼承抽象類ResultFilterAttribute
來實現自定義結果過濾器:
class MyResultFilter : ResultFilterAttribute
{
private readonly ILogger<MyResultFilter> _logger;
public MyResultFilter(ILogger<MyResultFilter> logger)
{
_logger = logger;
}
public override void OnResultExecuted(ResultExecutedContext context)
{
context.HttpContext.Response.Headers.Add("CustomHeaderName", "CustomHeaderValue");
}
public override void OnResultExecuting(ResultExecutingContext context)
{
if (context.HttpContext.Response.HasStarted)
{
_logger.LogInformation("Response has started!");
}
}
}
上面說過,IResultFilter
或IAsyncResultFilter
接口有一定的局限性,當授權、資源過濾器使管道短路或異常過濾器通過生成Result來處理異常等,會導致結果過濾器不被執行。但是,如果在這種情況下,我們也想要執行結果過濾器,那該咋辦呢?別慌,ASP.NET Core已經想到這種情況了。
那就是實現IAlwaysRunResultFilter
或IAsyncAlwaysRunResultFilter
接口,看這名字就夠直接了吧——始終運行:
public interface IAlwaysRunResultFilter : IResultFilter, IFilterMetadata { }
public interface IAsyncAlwaysRunResultFilter : IAsyncResultFilter, IFilterMetadata { }
中間件過濾器
中間件過濾器,其實是在過濾器管道中加入中間件管道。中間件過濾器的執行時機與資源過濾器一樣,即模型綁定之前和管道的其余部分執行之后執行。
要創建中間件過濾器,需要滿足一個條件,那就是該中間件必須包含一個Configure
方法(一般來說還會包含一個IApplicationBuilder參數用於配置中間件管道,不過這不是強制的)。
例如:
class MyPipeline
{
public void Configure(IApplicationBuilder app)
{
System.Console.WriteLine("MyPipeline");
}
}
[MiddlewareFilter(typeof(MyPipeline))]
public class HomeController : Controller { }
其他
IOrderedFilter
針對同一類型的過濾器,我們可以有多個實現,這些實現,可以注冊到不同的作用域中,而且同一個作用域可以有多個該過濾器類型的實現。如果我們將這樣的多個實現作用於同一個Action,這些過濾器實例的執行順序就是我們所要關心的了。
默認的,如果將同一作用域的同一類型的過濾器的多個實現作用到某個Action上,則這些過濾器實例的執行順序是按照注冊的順序進行的。
例如,我們現在有兩個操作過濾器——MyActionFilter1和MyActionFilter2:
public class MyActionFilter1 : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine("OnActionExecuting: MyActionFilter1");
}
public override void OnResultExecuted(ResultExecutedContext context)
{
Console.WriteLine("OnResultExecuted: MyActionFilter1");
}
}
public class MyActionFilter2 : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine("OnActionExecuting: MyActionFilter2");
}
public override void OnResultExecuted(ResultExecutedContext context)
{
Console.WriteLine("OnResultExecuted: MyActionFilter2");
}
}
然后將其作用到HomeController.Index
方法上,並且,先注冊MyActionFilter2,再注冊MyActionFilter1:
public class HomeController : Controller
{
[MyActionFilter2]
[MyActionFilter1]
public IActionResult Index()
{
return View();
}
}
當請求Home/Index
時,控制台的輸出如下:
OnActionExecuting: MyActionFilter2
OnActionExecuting: MyActionFilter1
OnResultExecuted: MyActionFilter1
OnResultExecuted: MyActionFilter2
但是,我們在開發過程中,很容易手滑將注冊順序弄錯,這時我們就需要一個手動指定執行順序的機制,這就用到了IOrderedFilter
接口。
public interface IOrderedFilter : IFilterMetadata
{
// 執行順序
int Order { get; }
}
IOrderedFilter
接口很簡單,只有一個Order
屬性,表示執行順序,默認值為0。Order
值越小,則過濾器的Before方法越先執行,After方法越后執行。
下面我們改造一下MyActionFilter1和MyActionFilter2,讓MyActionFilter1先執行:
public class MyActionFilter1 : ActionFilterAttribute
{
public MyActionFilter1()
{
Order = -1;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine("OnActionExecuting: MyActionFilter1");
}
public override void OnResultExecuted(ResultExecutedContext context)
{
Console.WriteLine("OnResultExecuted: MyActionFilter1");
}
}
public class MyActionFilter2 : ActionFilterAttribute
{
public MyActionFilter2()
{
Order = 1;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine("OnActionExecuting: MyActionFilter2");
}
public override void OnResultExecuted(ResultExecutedContext context)
{
Console.WriteLine("OnResultExecuted: MyActionFilter2");
}
}
此時,再次請求Home/Index
,控制台的輸出如下:
OnActionExecuting: MyActionFilter1
OnActionExecuting: MyActionFilter2
OnResultExecuted: MyActionFilter2
OnResultExecuted: MyActionFilter1
現在,我們看一下不同作用域的情況下,Order
是否生效。將MyActionFilter2作用域提升到控制器上。
[MyActionFilter2]
public class HomeController : Controller
{
[MyActionFilter1]
public IActionResult Index()
{
return View();
}
}
此時,再次請求Home/Index
,控制台的輸出如下:
OnActionExecuting: MyActionFilter1
OnActionExecuting: MyActionFilter2
OnResultExecuted: MyActionFilter2
OnResultExecuted: MyActionFilter1
哇,神奇的事情發生了,作用域為Action的MyActionFilter1竟然優先於作用域為Controller的MyActionFilter2執行。
實際上,Order
會重寫作用域,即先按Order
對過濾器進行排序,然后再通過作用域消除並列問題。
另外,若要始終首先執行全局過濾器,則請將Order設置為int.MinValue
:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(options =>
{
options.Filters.Add<MyActionFilter2>(int.MinValue);
});
}