ASP.NET Core 實(shí)現(xiàn) MVC filter “就近原則”
ASP.NET Core 實(shí)現(xiàn) MVC filter “就近原則”
Intro
在我們的日常開發(fā)中,我們可能會用到 MVC 里的 Filter 來實(shí)現(xiàn)一些切面邏輯,有一些 filter 可能只希望執(zhí)行一次,對于這樣的 filter 我們需要怎么做呢,下面就是一個示例
Sample
Filter 示例,這里我們以 AuthorizationFilter 為例,ActionFilter 也是一樣的
public?class?TestAuthFilter?:?AuthorizationFilterAttribute
{
????public?string?Role?{?get;?set;?}
????public?override?void?OnAuthorization(AuthorizationFilterContext?context)
????{
????????Console.WriteLine($"{nameof(TestAuthFilter)}({Role})?is?executing");
????}
}
AuthorizationFilterAttribute 是自己為 AuthorizationFilter 定義的一個類似于 ActionFilterAttribute 的東西,稍微簡化一些用作 Attribute 的 filter 定義,實(shí)現(xiàn)代碼如下:
public?abstract?class?AuthorizationFilterAttribute?:?Attribute,?IAuthorizationFilter,?IAsyncAuthorizationFilter
{
????public?virtual?void?OnAuthorization(AuthorizationFilterContext?context)
????{
????}
????public?virtual?Task?OnAuthorizationAsync(AuthorizationFilterContext?context)
????{
????????Guard.NotNull(context);
????????OnAuthorization(context);
????????return?Task.CompletedTask;
????}
}
再來看在 controller 代碼中的使用示例:
[TestAuthFilter(Role?=?"Admin")]
[Route("api/authTest")]
public?class?AuthTestController?:?ControllerBase
{
????[TestAuthFilter(Role?=?"User")]
????[HttpGet]
????public?IActionResult?Index()
????{
????????return?Ok();
????}
}
這個示例中,我們在 controller 和 action 上都加了 Filter,那么實(shí)際執(zhí)行的時候會怎么樣呢?
可以訪問一下這個接口來測試一下,控制臺輸出結(jié)果如下:
TestAuthFilter(Admin)?is?executing?
TestAuthFilter(User)?is?executing?

可以看到這個 filter 執(zhí)行了兩次,先執(zhí)行了 controller 定義的,然后執(zhí)行了 action 上定義的
但是其實(shí)我期望的是如果 action ?上有定義的話只執(zhí)行 action 上的 filter,離方法最近的 filter 才生效,其他的被覆蓋掉,不生效,我稱它為 filter 的 “就近原則”,怎么實(shí)現(xiàn)呢?實(shí)現(xiàn)起來其實(shí)也非常的簡單,改造我們的 filter 在執(zhí)行 filter 的邏輯之前可以加一個判斷,修改后的 filter 如下:
public?class?TestAuthFilter?:?AuthorizationFilterAttribute
{
????public?string?Role?{?get;?set;?}
????public?override?void?OnAuthorization(AuthorizationFilterContext?context)
????{
+???????if?(!context.IsEffectivePolicy(this))?return;
????????Console.WriteLine($"{nameof(TestAuthFilter)}({Role})?is?executing");
????}
}
然后我們再次訪問我們的接口,輸出結(jié)果如下:
TestAuthFilter(User)?is?executing?

可以看到 controller 上定義已經(jīng)沒有執(zhí)行了,只執(zhí)行 action 上定義的 filter 了。
IsEffectivePolicy 是 FilterContext 中的一個方法,是所有 filter 的上下文都會繼承的一個基類,無論是 AuthorizationFilter 還是 ActionFilter、ResourceFilter、ResultFilter、ExceptionFilter 都是可以像上面這樣用的,那它又是怎么實(shí)現(xiàn)的呢?
What's inside
我們可以反編譯或者直接去 Github 上看源代碼:https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Abstractions/src/Filters/FilterContext.cs
public?virtual?IList?Filters?{?get;?}
public?bool?IsEffectivePolicy(TMetadata?policy)?where?TMetadata?:?IFilterMetadata
{
????if?(policy?==?null)
????{
????????throw?new?ArgumentNullException(nameof(policy));
????}
????var?effective?=?FindEffectivePolicy();
????return?ReferenceEquals(policy,?effective);
}
public?TMetadata?FindEffectivePolicy()?where?TMetadata?:?IFilterMetadata
{
????//?The?most?specific?policy?is?the?one?closest?to?the?action?(nearest?the?end?of?the?list).
????for?(var?i?=?Filters.Count?-?1;?i?>=?0;?i--)
????{
????????var?filter?=?Filters[i];
????????if?(filter?is?TMetadata?match)
????????{
????????????return?match;
????????}
????}
????return?default;
}
Filters 是當(dāng)前請求對應(yīng)的 action 所涉及到的所有 filter 的集合,離 action 最近的 filter 也就是從 filter 集合中倒序找,倒數(shù)第一個就是最近的,就是優(yōu)先級最高的,在 filter 中就可以判斷如果當(dāng)前 filter 對象是否是最近的一個從而判斷是否要執(zhí)行 filter 中的邏輯
References
https://github.com/WeihanLi/WeihanLi.Web.Extensions/blob/dev/samples/WeihanLi.Web.Extensions.Samples/AuthTestController.cs https://github.com/WeihanLi/WeihanLi.Web.Extensions/blob/dev/samples/WeihanLi.Web.Extensions.Samples/Filters/TestAuthFilter.cs https://github.com/dotnet/aspnetcore/blob/v6.0.0/src/Mvc/Mvc.Abstractions/src/Filters/FilterContext.cs
