ASP.NET Core 实现 MVC filter “就近原则”
ASP.NET Core 实现 MVC filter “就近原则”
Intro
在我们的日常开发中,我们可能会用到 MVC 里的 Filter 来实现一些切面逻辑,有一些 filter 可能只希望执行一次,对于这样的 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 定义,实现代码如下:
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,那么实际执行的时候会怎么样呢?
可以访问一下这个接口来测试一下,控制台输出结果如下:
TestAuthFilter(Admin) is executing
TestAuthFilter(User) is executing
可以看到这个 filter 执行了两次,先执行了 controller 定义的,然后执行了 action 上定义的
但是其实我期望的是如果 action 上有定义的话只执行 action 上的 filter,离方法最近的 filter 才生效,其他的被覆盖掉,不生效,我称它为 filter 的 “就近原则”,怎么实现呢?实现起来其实也非常的简单,改造我们的 filter 在执行 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");
}
}
然后我们再次访问我们的接口,输出结果如下:
TestAuthFilter(User) is executing
可以看到 controller 上定义已经没有执行了,只执行 action 上定义的 filter 了。
IsEffectivePolicy
是 FilterContext
中的一个方法,是所有 filter 的上下文都会继承的一个基类,无论是 AuthorizationFilter
还是 ActionFilter
、ResourceFilter
、ResultFilter
、ExceptionFilter
都是可以像上面这样用的,那它又是怎么实现的呢?
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
是当前请求对应的 action 所涉及到的所有 filter 的集合,离 action 最近的 filter 也就是从 filter 集合中倒序找,倒数第一个就是最近的,就是优先级最高的,在 filter 中就可以判断如果当前 filter 对象是否是最近的一个从而判断是否要执行 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