关于asp.net core filters生命周期的探究
1.背景
昨天看了关于一篇 api 限流的文章,ASP.NET Core WebApi接口限流,作者给出了demo,写的很好,但是我看了一遍,api限流用actionfilterattribute,觉得很奇怪,难道说每次都是用的同一个filter。思考一番觉得自己还是写个demo验证以下,顺便看看源码是如何实现的,
2.demo
public class MyActionfilterAttribute:ActionFilterAttribute
{
private int a;
public MyActionfilterAttribute()
{
a = 50;
}
public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
Console.WriteLine($"begin");
++a;
Console.WriteLine(a.ToString()) ;
Console.WriteLine("end");
return base.OnActionExecutionAsync(context, next);
}
}
调试连续点击多次 出现如下结果,果然是同一个filter。看来确实是可以用actionfilter进行请求限制。
3.源码探究
感觉到奇怪的我转手就去看源码了,不对首先先搜一下有没有相关的文章,找到了一篇https://www.cnblogs.com/xiaoxiaotank/p/15622083.html 详细介绍了filters。点赞。
我的上一篇文章探究了以下controller在什么时侯被构建的,同时什么使用调用filter过滤器管道,其中就有如何获取filter,所以接着继续薅就是了。
ControllerActionInvokerCache
public (ControllerActionInvokerCacheEntry cacheEntry, IFilterMetadata[] filters) GetCachedResult(ControllerContext controllerContext)
{
// We don't care about thread safety here
if (cacheEntry is null)
{
var filterFactoryResult = FilterFactory.GetAllFilters(_filterProviders, controllerContext);
filters = filterFactoryResult.Filters;
//省略若干代码
}
else
{
// Filter instances from statically defined filter descriptors + from filter providers
filters = FilterFactory.CreateUncachedFilters(_filterProviders, controllerContext, cacheEntry.CachedFilters);
}
return (cacheEntry, filters);
}
逻辑比较简单,如果没有缓存那么就获取所有的filters,如果有缓存那么就创建没有缓存的filters。
FilterFactory
public static FilterFactoryResult GetAllFilters(
IFilterProvider[] filterProviders,
ActionContext actionContext)
{
var actionDescriptor = actionContext.ActionDescriptor;
var staticFilterItems = new FilterItem[actionDescriptor.FilterDescriptors.Count];
var orderedFilters = actionDescriptor.FilterDescriptors
.OrderBy(
filter => filter,
FilterDescriptorOrderComparer.Comparer)
.ToList();
for (var i = 0; i < orderedFilters.Count; i++)
{
staticFilterItems[i] = new FilterItem(orderedFilters[i]);
}
var allFilterItems = new List<FilterItem>(staticFilterItems);
// 由filter factory 决定哪个filter可以被缓存
// Execute the filter factory to determine which static filters can be cached.
var filters = CreateUncachedFiltersCore(filterProviders, actionContext, allFilterItems);
// Cache the filter items based on the following criteria
// 1. Are created statically (ex: via filter attributes, added to global filter list etc.) 2. Are re-usable
//缓存filter基于以下几点 : 首先静态生成 其次能够被重复使用
var allFiltersAreReusable = true;
for (var i = 0; i < staticFilterItems.Length; i++)
{
var item = staticFilterItems[i];
if (!item.IsReusable)
{
item.Filter = null;
allFiltersAreReusable = false;
}
}
if (allFiltersAreReusable && filterProviders.Length == 1 && filterProviders[0] is DefaultFilterProvider defaultFilterProvider)
{
// If we know we can safely cache all filters and only the default filter provider is registered, we can probably re-use filters between requests.
//如果我们知道我们能够安全的缓存这些filters,然后只有默认的filter providerb被注册,那么我们大 概可以在请求中重复使用这些过滤器
actionDescriptor.CachedReusableFilters = filters;
}
return new FilterFactoryResult(staticFilterItems, filters);
}
看到官方的注释确实我们是重复使用这些 filter的,应该是为了提高提高效率
主要的逻辑是 filterprovider来创建filter,然后我们拿到filter,有个属性为 IsReuable决定了我们是否可以重用这个filter
FilterFactory
private static IFilterMetadata[] CreateUncachedFiltersCore(
IFilterProvider[] filterProviders,
ActionContext actionContext,
List<FilterItem> filterItems)
{
// Execute providers
var context = new FilterProviderContext(actionContext, filterItems);
for (var i = 0; i < filterProviders.Length; i++)
{
filterProviders[i].OnProvidersExecuting(context);
}
for (var i = filterProviders.Length - 1; i >= 0; i--)
{
filterProviders[i].OnProvidersExecuted(context);
}
// Extract filter instances from statically defined filters and filter providers
//删除一些代码
var filters = new IFilterMetadata[count];
var filterIndex = 0;
for (int i = 0; i < filterItems.Count; i++)
{
var filter = filterItems[i].Filter;
if (filter != null)
{
filters[filterIndex++] = filter;
}
}
return filters;
}
进去看看 onprovidersexecuting方法 看就怎么产生的filters,默认的实现是
DefaultFilterProvider
public void OnProvidersExecuting(FilterProviderContext context)
{
if (context.ActionContext.ActionDescriptor.FilterDescriptors != null)
{
var results = context.Results;
// Perf: Avoid allocating enumerator and read interface .Count once rather than per iteration
var resultsCount = results.Count;
for (var i = 0; i < resultsCount; i++)
{
ProvideFilter(context, results[i]);
}
}
}
public void ProvideFilter(FilterProviderContext context, FilterItem filterItem)
{
if (filterItem.Filter != null) {return;}
var filter = filterItem.Descriptor.Filter;
if (filter is not IFilterFactory filterFactory) //标记的filter不是IFilterFactory类型
{
filterItem.Filter = filter;
filterItem.IsReusable = true; //那么可以重复使用
}
else
{
var services = context.ActionContext.HttpContext.RequestServices;
filterItem.Filter = filterFactory.CreateInstance(services);//创建实例
filterItem.IsReusable = filterFactory.IsReusable;
ApplyFilterToContainer(filterItem.Filter, filterFactory);
}
}
上面的注释就是创建filter的主要逻辑了,如果是静态构造那么可用重复使用,然后IFilterFactory主要有两种类型,一种是ServiceFilterAttribute,要求该过滤器和构造函数参数要在DI容器中注册,另一种是TypeFilterAttribute,部分参数自己提供,部分参数ioc提供。这两种filter.IsReusable=false.
看看如果有缓存那么是如何创建filters
FilterFactory
public static IFilterMetadata[] CreateUncachedFilters(
IFilterProvider[] filterProviders,
ActionContext actionContext,
FilterItem[] cachedFilterItems)
{
//删除一些代码
if (actionContext.ActionDescriptor.CachedReusableFilters is { } cached)
{
return cached; //如果都是可以缓存的filter直接返回
}
//深拷贝一份数据
// Deep copy the cached filter items as filter providers could modify them
var filterItems = new List<FilterItem>(cachedFilterItems.Length);
for (var i = 0; i < cachedFilterItems.Length; i++)
{
var filterItem = cachedFilterItems[i];
filterItems.Add(
new FilterItem(filterItem.Descriptor)
{
Filter = filterItem.Filter,
IsReusable = filterItem.IsReusable
});
}
return CreateUncachedFiltersCore(filterProviders, actionContext, filterItems);
}
深度拷贝了一份数据,但是filterItem.Filter没有拷贝进来,复制的是filterItem.Descriptor属性所以再调用CreateUncachedFiltersCore的时候我们可以复用静态构造的filter,但是其他的就要重新构造一份了。
4.总结
1.静态构造的filter会复用,但是如果类型是 servicefilter或者是 typefilter的话则每次会重新构建新的
2.复用的逻辑还是看类型甚至我们还可以自己扩展一个类型,然后自己去构建对象。
2.翻了一遍源码,写bug更有心得了。