关于asp.net core filters生命周期的探究

发布时间 2023-03-22 21:16:23作者: 果小天

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会复用,其他的会重新构造

2.翻了一遍源码,写bug更有心得了。