ASP.NET Core 实现 MVC filter “就近原则”
共 3513字,需浏览 8分钟
·
2022-03-16 15:19
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