Spiga

ASP.NET Core 7 源码阅读6:Filter

2023-08-20 21:32:52

一、快速认识Filter

1. 流程衔接点

MVC(Filter+Controller+Action+Result)流程和前面的各种中间件Middleware处理,是如何衔接起来的?

  1. Http请求穿过一系列中间件,最终由路由RoutingMiddleware匹配得到RouteEndpoint
  2. 在EndpointMiddleware里面会执行其RequestDelegate,委托执行时就是FilterController-Action-Result
  3. 该委托在ControllerRequestDelegateFactory里面来构建的,里面通过ControllerActionInvoker来调用

这里有个新名字叫 FilterPipeline

2. 快速认识Filter

Filter是MVC里面核心组成部分,有着丰富的种类,交叉的顺序,复杂的嵌套,很有挑战! Filter分2种:

  1. 声明特性,实现Filter接口:CustomSimpleShowActionFilterAttributeCustomSimpleShowAsyncActionFilterAttributeCustomSimpleShowDoubleActionFilterAttribute:一个Filter里面既有同步,又有异步,只执行异步!能改参数,能改结果,非常强大注意异步版本的结果修改坑
  2. 控制器类实现Filter接口包括Action同步异步-Result同步异步---控制器生效,全部Action都有效

3. AOP面向切面编程

要说Filter,得理解下AOP面向切面编程Aspect Oriented Programming

POP面向过程,没有封装没有边界,难以应对复杂需求OOP面向对象,有封装有边界,能应付复杂需求,但原子---类---不容破坏,否则会影响稳定性---开闭原则

AOP能在不破坏封装的前提下,去额外扩展功能比如ActionFilter,啥都没修改,就能增加执行逻辑AOP的好处:

  1. 聚焦业务逻辑,轻松扩展功能
  2. 代码复用,集中管理

4. 框架中的AOP

现代化开发框架中,要灵活应用AOP面向切面编程思想: ASP.NET Core三层AOP

  1. 中间件:离MVC比较远,面向与请求级的事儿
  2. Filter:更贴近MVC,面向单个请求,更细一些
  3. IOC容器的AOP扩展:细化到具体业务,跟分层封装相关

5. Filter多种注册

专门写个RegisterWayShow来展示:

  1. Action:直接标记特性---只对方法生效
  2. Controller:控制器标记特效型-----控制器下全部Action生效
  3. 全局:在AddMVC时添加到Options-----进程内全部Action生效

还有别的方式吗?

  1. ModelConvention—程序初始化的时候留了一个后门,可以额外设置Filter----CustomControllerModelConvention—效果没出来, ToDO
  2. 控制器类实现Filter接口,也是添加的Filter

常规是3种方式---超纲是5种方式

6. Filter执行顺序

这里讨论的是相同类型的Filter,以ActionFilter为例子:

  1. Order默认0,相同注册的Filter是从小到大执行,可以为负数---执行Filter之前会有个Order排序,然后再遍历执行
  2. 相同Order相同注册,从先注册到后注册---Filter是反射获取的,就是按照上下顺序来的
  3. 从全局---控制器---Action的顺序执行------扫描dll后, updatecollection时,是先全局,再控制器,再Action

7. Filter依赖注入

Filter是基于特性标记的,所以Filter默认是不能IOC注入服务的, why?

因为特性attribute是编译时确定的—所以不能注入服务

Filter做依赖注入的4种方式:

  1. 全局注册
  2. ServiceFilter, IOC注册,不支持额外参数
  3. TypeFilter,支持额外参数
  4. 自定义实现IFilterFactory—下文解析

8. 关于IFilterFactory

  1. 框架里获取Filter有2种情况: Filter和FilterFactory,遇到后者会传递容器实例去调用CreateInstance来获取Filter实例----看DefaultFilterProvider
  2. 看看ServiceFilter源码----TypeFilter源码----其实都是创建实例,前者靠IOC,后者靠反射+IOC容器(所以能支持参数)
  3. 自定义实现IFilterFactory: CustomIOCFilterFactory也没啥难度,跟ServiceFilter几乎一模一样

IsReusable属性,带来一个Filter实例重用的问题

9. Filter实例重用

  1. 一个方法---N个Filter---每次请求来了,都要实例化N次的Filter?--其实会缓存的,可以测试验证----CustomDIActionFilterAttribute, filter默认都是
  2. 如果某个Filter需要实时创建,用完释放? ---只有一个办法就是FilterFactory,且指定IsReusable = false---CustomDisposeActionFilterAttribute-注意IOC注册Filter的生命周期------可以每次都创建,不再复用,且自动调用Dispose方法

10. FilterFactory源码

其实就是Filter的全流程回顾:

  1. Filter是基于特性的,在metadata,所以在AddMVC就收集起来了--包括对ModelConversion的支持
  2. 准备ControllerActionInvoker时,会构造并缓存Filter,这里会看是否是IFilterFactory,是的话调用CreateInstance,创建出Filter-(起点ControllerRequestDelegateFactory )
  3. 里面会去获取实例,看属性IsReusable来确定是否重用

11. 多Filter认知

  1. Authorization Filters
  2. Resource Filters
  3. Exception Filters
  4. Action Filters
  5. Result Filters
  6. 其实还有个AlwaysRunResultFilter

二、FilterPipeline

1. Filter流程-上帝视角

Filter主要是响应时生效,同时在启动环节也有相关动作,贯穿始终:

  1. Filter是基于特性,是启动环节里面UpdateCollection()时完成的收集和实例化(含FilterFactory),最终是每个Action都化作一个EndPoint,里面有个metadata属性,保存的特性---里面包括了全局/控制器/Action标记,以及ModelConvertions 和控制器父类里面的Filter
  2. 响应环节--EndpointMiddleware里面找到Endpoint,执行其RequestDelegate委托,这里在调用方法前,会Filter实例加工(FilterFactory创建实例+原理Filter实例+Filter实例缓存管理)
  3. ControllerActionInvoker为起点,开始执行系列Filter+CAR,包含39个状态流转+8个Action状态+8个AlwaysRunResult状态,最终完成处理

2. Filter收集和实例化

Filter的收集发生在程序启动时:

  1. 从MapRouteController开始, DefaultActionDescriptorCollectionProvider的UpdateCollection()方法--ApplicationModelFactory.CreateApplicationModel----DefaultApplicationModelProvider.DefaultApplicationModelProvider,里面就是各种收集Filter
  2. 会依次看到收集全局/控制器/Action的,且在控制器里面还有检测controller是否实现Fiter接口并增加对应的Filter---这里会实例化Filter或FilterFactory
  3. 收集完后,会执行ApplicationModelCoversion,支持添加各种Filter

结论:

  1. Filter的条件是:特性+实现IFilterMetadata接口(FilterFactory也算)
  2. 这里只是简单实例化IFilterMetadata,没管IFilterFactory
  3. 基础的Filter顺序:全局-控制器特性-控制器继承-Action-ModelConversion ,也是因为这个顺序收集的
  4. ModelConversion的支持
  5. Controller父类里面的IActionFilter和IResultFilter

3. Filter加工和缓存

Filter请求真的来了,经过路由、鉴权、授权后,准备正式执行Filter-C-A-R

  1. ControllerRequestDelegateFactory的CreateRequestDelegate里面委托开始,看到controllerActionInvokerCache.GetCachedResult里面完成的
  2. 然后是FilterFactory. GetAllFilters直接获取全部的Filter实例,然后再CreateUncachedFiltersCore 创建下没有缓存的Filter实例
  3. DefaultFilterProvider里面完成,其实第一步先把IFilterFactory 的创建Filter(之前没管),且保存IsResureable 属性(默认都是true)---会区分是否IFilterFactory,去创建对象并返回
  4. 期间完成缓存管理,下次请求时就只管IsResureable为false的

4. InvokeFilterPipeline

InvokeFilterPipeline开始整个流程的执行

  1. 准备好各种参数, ControllerActionInvoker的父类ResourceInvoker.InvokeAsync----实际是InvokeFilterPipelineAsync开始Filter管道调用
  2. InvokeFilterPipelineAsync(finally会释放资源)---1 授权fillter---2Resoucefilter---3Exceptionfilter----逻辑1:实例化控制器-----逻辑2:自动绑定参数----然后是Action-----Result---Formater

至此,开始正式进入Filter执行!

5. FilterPipeline概述

  1. ControllerActionInvoker和父类ResourceInvoker
  2. 39个状态流转State +4个作用域Scope
  3. 8个Action状态+2个作用域Scope
  4. 8个AlwaysRunResult状态

Why 这么复杂?? 细思恐极!

Filter是AOP---靠的是框架反射调用Action—期间去检测上面的特性,根据有啥特性多执行点动作---实现了只需加个特性就能增加功能的AOP效果

实际运行时已经不是特性了—已经转换成Filter实例的数组---执行的时候就内定个顺序来执行

因为逻辑太复杂---为了好分析,就做成了多个state流转 因为嵌套问题,每个Filter自己都可以嵌套,都是俄罗斯套娃,所以需要Scope

6. Filter流程快读

快速调试源码走流程,能总结啥知识:

  1. 执行顺序问题—5(6)大类Filter的之间的执行顺序是框架写死的
  2. 生命周期问题---谁包裹了谁,谁在外面谁在里面—也是写死的
  3. 作用域范围---Exception控制器实例化开始生效

7. FilterCursor

管理FilterItem实例集合,用于遍历获取Filter实例的

  1. 每一类Filter开始,都得重置索引
  2. 一个Filter同时支持同步+异步,优先执行异步版本,同步的不执行

8. 递归与goto

递归(自己调用自己)还是goto(强制跳转某一行)?

  1. 递归好看,好理解,但是浪费性能
  2. goto就是不好看懂,轻易不要goto---不好阅读,难以控制,除非为了性能

框架是二者结合用的,即有递归 也有goto

这块儿很复杂,要啃硬骨头

三、AuthorizationFilter

1. AuthorizationFilter实操

AuthorizationFilter:实现的IAuthorizationFilter, IFilterMetadata,典型的Filter,含具体的检测逻辑和处理方式前面的[Authorize] :实现的IAuthorizeData,配合鉴权授权中间件的,只是标记,靠的是鉴权授权中间件解析

  1. 作用:中间件执行完了,实例化控制器之前,其他Filter之前,完成登陆授权验证,MVC的授权Filter
  2. 实现IAuthorizationFilter接口+Attribute子类
  3. CustomAuthorizationFilterAttribute实现,请求来了就会先执行

2. AuthorizationFilter细节

  1. 同步+异步? -----只执行异步
  2. 匿名支持---------2种不同的匿名
  3. 用户信息获取: Cookie-Session----URL参数---JWT其实都行
  4. 指定响应结果---不同请求响应---

AuthorizationFilter和鉴权授权中间件怎么选?

推荐是鉴权授权中间件形式,更早处理,更标准化

除非不熟悉,或者旧代码迁移,才用这个

3. AuthorizationFilters源码

  1. AuthorizationBegin
  2. AuthorizationNext
  3. AuthorizationAsyncBegin
  4. AuthorizationAsyncEnd
  5. AuthorizationSync
  6. AuthorizationShortCircuit
  7. AuthorizationEnd

基本流转: 1---2—3(5)---有Result就去6终止—否则再到2---递归下去—直到全部Filter执行完---7—去Resource

3是异步,完成去4----5就自己完成

4. AuthorizationFilters完整过程

  1. InvokeBegin 等同于AuthorizationBegin,重置了下Filter集合的索引到0 然后AuthorizationNext
  2. AuthorizationNext找到IAuthorizationFilter(或者异步版本IAsyncAuthorizationFilter),有就去到对应的Filter执行,没有就去AuthorizationEnd(本流程完结)
  3. 执行IAuthorizationFilter的OnAuthorization,如果指定Result则去到AuthorizationShortCircuit,这里会执行AlwaysRunResultFilters,跳过其他环节;如果Result为空则去到AuthorizationNext---循环嵌套
  4. 执行IAsyncAuthorizationFilter异步版本---涉及到await,所以会判断是否IsCompletedSuccessfully,没有就返回上层再await一次,下一步状态都是AuthorizationAsyncEnd------AuthorizationAsyncEnd里面检测Result,不会空则去AuthorizationShortCircuit,这里会执行AlwaysRunResultFilters,跳过其他环节;如果Result为空则再去AuthorizationNext---循环嵌套
  5. AuthorizationNext-找不到Filter,则去到AuthorizationEnd,结束

5. 知识点

  • 断路器:中断后续流程,走非常规流程:
  1. 指定context.Result—就不再继续了
  2. 如果有AlwaysRunResultFilter会执行
  • 双接口:一个Filter,同时实现同步方法和异步方法,只执行异步方法

  • 授权验证嵌套:

    嵌套是如何实现的,标记多个AuthorizationFilter

    1. 写法简单,直接多重标记
    2. 源码也算简单,可以直接goto AuthorizationNext ----因为不是俄罗斯套娃,只是前面横切一层,没有前后包裹

四、ResourceFilter

1. ResourceFilter演练

快速认知IResourceFilter和常用功能演练

  1. 实现IResourceFilter接口+特性子类
  2. 发生在Authorization之后, Exception之前----控制器实例化之前,最多的应用是做缓存
  3. CustomShowResourceFilterAttribute实现
  4. CustomShowAsyncResourceFilterAttribute实现(有差异)
  5. 支持嵌套(默认有个TempData的资源Filter)
  6. 测试流程, ResourceFilter执行顺序

2. ResourceFilter缓存实现

  1. ResourceFilter实现缓存,一进 一出 一保存
  2. Result是断路器,直接保存的是IActionResult---然后去了View
  3. ResourceFilter通常用来做缓存,注意缓存的是什么? IActionResult—还是要执行Result的,但是不执行Action
  4. 缓存命中后ResultFilter不生效,但AlwaysRunResultFilter生效

3. ResourceInvoker-ResourceFilter

等同于上帝视角:

  1. ResourceBegin
  2. ResourceNext
  3. ResourceAsyncBegin
  4. ResourceAsyncEnd
  5. ResourceSyncBegin
  6. ResourceSyncEnd
  7. ResourceShortCircuit
  8. ResourceInside
  9. ResourceInsideEnd
  10. ResourceEnd

执行第一个Resource的Executing—没有中断—再执行Next----又是去执行 第二个Resource的Executing---递归下去,直到没有Resource— 然后去ResourceInside---然后回来再一层层的Executed

4. ResourceFilter完整过程

需要在源码项目增加2个Filter且标记上,一个是异步版本,另一个是继承同步、异步的双支持版本-----项目内置了一个同步版本的: 第一次,同步版本:

  1. 从ResourceBegin开始,先重置Filter集合的索引,当时Scope是Invoked---然后去ResourceNext
  2. ResourceNext找到SyncResourceFilter,先初始化_resourceExecutingContext,然后goto ResourceSyncBegin_
  3. ResourceSyncBegin对应的Filter执行OnResourceExecuting,然后检测Result,有值会初始化_resourceExecutedContext(表示请求已被处理,对其他Filter断路),然后去到ResourceShortCircuit(结果有了,中断);
  4. Result没有值,则去InvokeNextResourceFilter()----修改Scope为Resource,进行递归调用,包括里面所有的Filter-Aciton-Result等-----递归执行完了,才再去ResourceSyncEnd

SyncResourceFilter先执行OnResourceExecuting,然后去递归,都完成才能去到自己的ResourceSyncEnd

第二次,异步版本:

  1. 在ResourceNext找到AsyncResourceFilter,先初始化ResourceExecutingContext,然后goto ResourceAsyncBegin
  2. ResourceAsyncBegin对应的Filter执行OnResourceExecutionAsync-----(这里又拓展了个InvokeNextResourceFilterAwaitedAsync做递归调用)-----然后又去到ResourceNext,继续递归
  3. 递归完成后,走到ResourceAsyncEnd

其中InvokeNextResourceFilterAwaitedAsync又是递归调用,跟前面是一样的

第三次,双版本:

  1. 执行第3个Filter---完成OnResourceExecuting之后继续递归---没有Filter了,就到了ResourceInside---开始内部流程(从ExceptionBegin开始)—直到完成ActionEnd指向ResourceInsideEnd
  2. ResourceInsideEnd根据Scope判断在递归里面—就指向ResourceEnd,里面也通过Scope判断在递归里面,返回ComplateTask,就是之前递归的动作完成了—可以继续后续的动作
  3. 如果是同步的,就到ResourceSyncEnd,里面会执行OnResourceExecuted---然后又去到ResourceEnd---根据Scope决定是否在递归
  4. 如果是异步的,就到ResourceAsyncEnd:这里会检测_resourceExecutedContext,如果不为空则表示别的Filter已处理,这里直接去ResourceEnd;如果_resourceExecutedContext不为空则初始化一下,再去检测Result,有值则去到ResourceShortCircuit(结果有了,中断);没有值,则去ResourceEnd-----根据Scope决定是否在递归
  5. 递归剥洋葱—一直到Scope变成Invoke,就去到InvokeEnd

5. 知识点

  • Scope和递归

    嵌套是如何完成?一定得有Scope!

    1. 递归是为了嵌套,会有多个RecoursFilter,又是相互包裹
    2. 同步方法靠的是InvokeNextResourceFilter,指定Scope为Resource
    3. 异步方法靠的是InvokeNextResourceFilterAwaitedAsync,其实调用的也是上面的这个,指定Scope为Resource
    4. 然后在流程完结点ResourceEnd,去通过Scope来区分是当前是嵌套(回递归流程)还是外层流程(直接到InvokeEnd结束)
  • ResourceFilter异步源码

    AsyncResourceFilter是如何实现的委托回调?ResourceExecutionDelegate是个啥?

    这是个委托,里面是Next方法(是后续的全部流程)跟同步的没多大区别,就是把递归调用的那个方法InvokeNextResourceFilter() 包装成 这个委托InvokeNextResourceFilterAwaitedAsync

  • ResourceFilter精准时机

    1. OnResourceExecuting:在AuthorizationFilter执行后,在Exception-ControllerAction-Result等之前执行-----支持嵌套
    2. OnResourceExecuted: A 发生异常,然后被异常Filter处理后(等于Result结束)B 在Action-Result--ResultFilter执行后----等着Result都处理完才执行的
    3. 假如OnResourceExecuting拦截到做了短路----给Result赋值了ActionResult----会执行AlwaysRunResultFilter流程---里面包括全部的AlwaysRun+ActionResult.Invoke----但是不会执行ResultFilter,其他中间环节都没了
  • Result处理

    1. 如果缓存没有命中,会在ResultFilter-AlwaysRun-ActionResult啥的都执行完后执行缓存
    2. 如果缓存命中了,会执行个Canceled = true,然后去ResourceShortCircuit,其实就是去InvokeAlwaysRunResultFilters----里面包括全部的AlwaysRun+ActionResult.Invoke----但是不会执行ResultFilter
    3. AlwaysRunResultFilter是有执行的----授权失败也是的,缓存拦截也要执行----ResultFilter是为指定的结果服务---- AlwaysRunResultFilter为全部结果服务的,因为其他情况,也得有个Result
  • 资源Filter异常

    会被ExceptionFilter抓住吗? --不会的,因为还没开始exception,只能靠全局!

    1. OnResourceExecuting的异常----没有意义,因为都直接中断流程---直接异常
    2. 如果在外面包一层RecourseFilter的配对组合,却能完整执行!! --是真的----如果在递归流程中, try-catch,里面会吞掉异常—然后保证外层的Filter能执行完—然后再检测执行过程中的异常,再抛出
    3. OnResourceExecuted的异常----控制台有异常信息,没人管----但是页面还是能正常返回!! -----因为这个时候Result+各种Filter都已经执行完了, response都已经写好了,虽然异常,但不影响的---
    4. 如果在外面包一层RecourseFilter的配对组合,却能完整执行!! ---是真的,跟第2个一样

五、ExceptionFilter

1. ExceptionFilter演练

快速认知IExceptionFilter和常用功能演练

  1. 实现IExceptionFilter接口+特性子类
  2. CustomShowExceptionFilter实现
  3. CustomShowAsyncExceptionFilter实现
  4. ExceptionHandled属性
  5. 基本流程验证(分开验证)
  6. 初始化在Resource的Inside里面,控制器实例化之前,异常发生后触发生效
  7. 关于ResultFilter和AlwaysRunResultFilter--- ExceptionHandled没有处理,则啥result都不执行--- ExceptionHandled 处理掉Always会执行, Result不执行

2. ExceptionFilter细节

异常Filter使用时各种小细节:

  1. 异常处理,注入日志组件和其他组件---
  2. 不同类型请求,响应不同的结果---Ajax请求还是html请求
  3. 不同异常类型,不同处理方式---
  4. 多层嵌套,不同异常类型不同处理---自己实现
  5. 嵌套后发现, Order执行的顺序居然是从大到小?

3. ResourceInvoker-ExceptionFilter

等同于上帝视角:

  1. ExceptionBegin
  2. ExceptionNext
  3. ExceptionAsyncBegin
  4. ExceptionAsyncResume
  5. ExceptionAsyncEnd
  6. ExceptionSyncBegin
  7. ExceptionSyncEnd
  8. ExceptionInside
  9. ExceptionHandled
  10. ExceptionEnd

启动—响应---AuthorizationFilter---ResourceFilter—里面的Inside才初始化ExceptionFilter------初始化完(包含嵌套)---控制器实例化---Action参数绑定-- ActionFilter---Action---都在范围内---然后是结束ExceptionFilter---然后再去执行Result-ResultFilter—AlwaysRunResult(不在内)

4. ExceptionFilter完整过程

先理解同步的,最好只标记一个同步ExceptionFilter的测试

  1. (ResourceInside里面开始的)ExceptionBegin,先重置Filter集合的索引,然后去ExceptionNext
  2. ExceptionNext找到IExceptionFilter(或者异步版本),找到就后续不同处理流程-------没找到则是2种情况:要么ExceptionFilter已经嵌套完了,也就是Scope是Exception,进入ExceptionInside,实际是就是ActionBegin;要么根本就没有ExceptionFilter,直接去ActionBegin---鲁迅的枣树
  3. 先来理解同步的ExceptionSyncBegin,直接开始递归,指定Scope为Exception----如果没有其他ExceptionFilter的话,就应该走到ExceptionInside,也就是ActionBegin,去执行里面的逻辑,如果都顺利执行完了,就到ActionEnd,然后根据Scope=Exception,可以return了---表示之前递归的InvokeNextExceptionFilterAsync完成,可以进入ExceptionSyncEnd
  4. ExceptionSyncEnd里面是检测exceptionContext?.Exception和exceptionContext?.Exception属性,有未处理异常则会触发filter.OnException方法------然后走向ExceptionEnd---没有未处理的异常,也是走向ExceptionEnd

理解ExceptionEnd,分3种情况:

  1. 作用域是exception,说明在递归里面,结束当前递归,回到上一层 ;
  2. 是最外层的exceptionfilter,则检测结果,如果context不为空,要么有结果了;要么异常清空了;要么异常被处理好了,则去到ExceptionHandled,执行alwaysrunresult去了;否则抛异常Rethrow
  3. 一切正常,去执行InvokeResultFilters,后续的Result处理和各种ResultFilter—exception的终点

再说说抛异常Rethrow,就是抛异常,进入递归的方法catch,然后写入到Invoker的context里面

再说说State.ExceptionHandled: Result为空则给个EmptyResult(),然后是执行alwaysrunresult,执行完后,结束exception流程,回到上层的ResourceInsideEnd---- -异常处理结束

Action里面有异常,抓住处理了,没有执行Result,但AlwaysRunResultFilter是要执行的! 抓住但不处理异常呢?啥Filter都不执行了, Rethrow了,是异常没处理

异步版本稍微有点区别,也可以说没有区别---最好只标记一个异步ExceptionFilterAsync的测试

  1. 从ExceptionNext先到ExceptionAsyncBegin,直接开始递归,指定Scope为Exception----如果没有其他ExceptionFilter的话,就应该走到ExceptionInside,也就是ActionBegin,去执行里面的逻辑,如果都顺利执行完了,就到ActionEnd,然后根据Scope=Exception,可以return了---表示之前递归的InvokeNextExceptionFilterAsync完成,可以进入ExceptionAsyncResume(基本完全一致)
  2. ExceptionAsyncResume里面是检测exceptionContext?.Exception和exceptionContext?.Exception属性,有未处理异常则会触发filter.OnExceptionAsync方法------然后走向ExceptionAsyncEnd---没有未处理的异常,则是走向ExceptionEnd
  3. ExceptionAsyncEnd里面啥也没干,记录点日志,还是去到ExceptionEnd

几乎是一样的,虽然多了个Resume状态,但没啥用

5. 理解递归嵌套

如何嵌套,靠的就是递归--- InvokeNextExceptionFilterAsync()—指定下Scope同步异步嵌套,像一个栈stack,压进去的不同的ExceptionFilter默认是从小到大的,所以是先进个order=5的,再进个order=10的 最后进个order=13的

Filter顺序是从小到大--异常触发是冒泡式,从大到小

  1. 像栈一样先进后出,先处理13的,然后是10、是5
  2. Action有异常,然后10没有处理异常, ExceptionHandled=false,继续5处理
  3. Action有异常,然后10处理掉了, ExceptionHandled=true, 5就不会触发了
  4. 如果Filter13自身发生异常呢? AAAA 都不指定ExceptionHandled=true,会跳过1个10,只执行5,且最终结果是异常; BBBB 其他后续Filter指定ExceptionHandled= true,是可以处理Filter内部的异常(要注意,会跳过一个)

6. Try-Catch实现

ExceptionFilter是处理异常的,一定得有Try-Catch,在哪里?在InvokeNextExceptionFilterAsync!其实在他的await方法

  1. 无论同步还是异步,都是要去InvokeNextExceptionFilterAsync,这里就有Try-Catch,但不是这个
  2. 方法里面调用Next是执行后续流程,万一发生异常,就进入内部方法Awaited----里面上来先await lastTask,其实还是继续异常,然后这里catch住,保存到_exceptionContext属性----回到InvokeNextExceptionFilterAsync的递归继续,这次异常就被装在属性里面了,支持后续的处理

异常Filter的实现:任意环节其实都在自己处理异常,保证流程能顺序进行---然后Rethow告诉上一层---Exception靠的就是Awaited时检测异常,保存到属性—其他地方都靠这个数据做判断-----可以看看Action里面的异常处理方式,也是先包起来放在属性里面----保证当前的Filter配套能完成,然后再rethrow

任意层指定了ExceptionHandled=true,都会跳到ExceptionHandled,表示处理完—BBBB的原因-------AAAA是因为Filter的异常,直接在InvokeNextExceptionFilterAsync里面异常,会跳过一层递归

7. ExceptionFilter范围

ExceptionFilter能处理哪些异常,哪些异常又处理不到?刚才都在聊原理,源码,很枯燥,需要理解—但这里才有答案可以按时间顺序,执行顺序:

  1. ExceptionFilter是在ResourceFilter之后, Inside初始化的---此前的异常都不能抓住(程序启动环节各种初始化的异常、 dll扫描环节、中间件的初始化、请求来时穿过的中间件IN方向, authorizationFilter、 ResourceFilter)
  2. 初始化后会执行的动作都可以处理(控制器的初始化、绑定Action参数、ActionFilter、 ActionInvoke—Action调用的其他类库异常) -- ExceptionFilter自己的异常(很特殊)
  3. Exception结束是进入InvokeResultFilterAsync(),这些往后其实都不能处理的(ResultFilter----AlwaysRun----Result执行--- ResourceFilter 的Executed---各种中间件的Out方向)

8. 知识点:完整异常处理方案

范围外的异常如何处理?

  1. 日常开发最高频的错误,大部分都在范围内
  2. 也有些处理不到的怎么办呢?

全局异常处理+ExceptionFilter结合

  1. ExceptionFilter:知道执行细节ExceptionContext---所以可以给出很多自定义的结果
  2. 全局:就比较粗略,有错误堆栈,做的粗一下,通用一下

六、ActionFilter

1. 快速认知IActionFilter和常用功能演练

  1. 实现IActionFilter接口+特性子类
  2. CustomShowActionFilter---CustomAsyncShowActionFilter---CustomShowDoubleActionFilter
  3. 执行前实例化控制器和BindArgumentsAsync
  4. 支持嵌套
  5. 支持IOC注入
  6. 基本流程验证

2. ActionFilter细节

  1. 执行前已完成参数绑定,所以OnActionExecuting可以获取参数,甚至可以修改
  2. OnActionExecuting还能通过context.Result指定结果,完成断路
  3. OnActionExecuted是Action执行后,也能指定Result去断路(内层的ActionFilter)
  4. 断路后ResultFilter还执行吗? AlwayRunResultFilter呢? --都是正常执行
  5. 注意OnActionExecuting和OnActionExecuted参数不一样,能做的事儿也不一样

3. ResourceInvoker-ActionFilter

  1. ActionBegin
  2. ActionEnd
  3. ActionBegin
  4. ActionNext
  5. ActionAsyncBegin
  6. ActionAsyncEnd
  7. ActionSyncBegin
  8. ActionSyncEnd
  9. ActionInside
  10. ActionEnd

父类ResourceInvoker只有2个状态

子类ControllerActionInvoker有8个状态 (2个重复的)支持嵌套的,核心代码在ControllerActionInvoker

4. ActionFilter完整过程

ResourceInvoker里面

  1. 从进入Filter流程---经过AuthorizationFilter多重拦截,再走过ResourceFilter,然后被多层ExceptionFilter嵌套后,终于走到ExceptionInside,也就是State.ActionBegin
  2. 结果ActionBegin调用了一个abstract方法,其实现在ControllerActionInvoker,然后这里只有ActionBegin和ActionEnd2个状态,真实状态流转放到子类去了
  3. 到了ControllerActionInvoker还是从ActionBegin开始,然后也有递归,最终都是进入到ActionEnd

套路都一样,为啥要分开呢?没去细究,应该有个别的点要分一下,,毕竟是疯狂嵌套

ControllerActionInvoker里面

  1. 到了ControllerActionInvoker首先开启Scope=ActionBegin,然后去递归Next,从新的ActionBegin开始
  2. ActionBegin先重置Filter集合的索引,然后实例化控制器了(详见知识点1实例化控制器),然后BindArgumentsAsync()绑定Action需要的参数(详见知识点2绑定参数),然后去ActionNext
  3. ActionNext还是3件事儿,找到IAsyncActionFilter就执行异步版本去ActionAsyncBegin;否则是找到IActionFilter就执行同步版本去ActionSyncBegin;都没有了则去执行ActionInside(具体的Action动作)

同步Filter实现:

  1. ActionSyncBegin会先执行OnActionExecuting,结果有2种情况:
    • 如果Result为空,会执行InvokeNextActionFilterAsync(注意名字跟之前不一样)
    • 如果Result不为空,则直接去到ActionEnd了;
  2. 先看A情况, InvokeNextActionFilterAsync其实是设置作用域为Action,去递归调用内部流程(其他ActionFilter和ActionMethod),递归完成后的流程还是走到自己的ActionSyncEnd-----ActionSyncEnd会执行OnActionExecuted,等于一切正常完成,然后进入到ActionEnd
  3. 再看B情况,直接到ActionEnd-----如果scope为Action,说明嵌套递归了,则保存结果后return结束当前Filter(表明不会执行Action和ActionExecuted),继续外层Filter继续调用,直至完成------------否则是有异常抛异常--------最后是保存结果,完成当前Filter继续后面的流程,最终是回到父类的ActionEnd
  4. 最外层ActionEnd,其实是Action已经完成了----然后开始处理外层包裹的东西:如果在Exception里面,这return去处理;然后是去执行InvokeResultFilters,里面是Result+Filter,都完成了,还得去到ResourceInsideEnd,因为可能都包裹在Resource里面

异步Filter实现

  1. ActionAsyncBegin会执行OnActionExecutionAsync,里面参数InvokeNextActionFilterAwaitedAsync其实也就是先判断Result,有执行就通过抛异常终止当前流程;否则还是执行InvokeNextActionFilterAsync----跟同步是一样的
  2. 如果InvokeNextActionFilterAwaitedAsync正常完成,走向ActionAsyncEnd,然后进入到ActionEnd
  3. 最外层ActionEnd,其实是Action已经完成了----然后开始处理外层包裹的东西:如果在Exception里面,这return去处理;然后是去执行InvokeResultFilters,里面是Result+Filter,都完成了,还得去到ResourceInsideEnd,因为可能都包裹在Resource里面

5. 知识点

  • 继承实现ActionFilter

    1. 多一种方式声明ActionFilter(默认就有个),靠的是继承
    2. Controller默认接口实现过IAsyncActionFilter和IActionFilter,可以override覆写,看源码知道会优先执行异步版本---(这个是当成了控制器的ActionFilter—order=0,在其他控制器的filter最后)
    3. 生效范围是控制器的全部Action
    4. 框架还内置了一个UnsupportedContentTypeFilter
  • 断路器问题

    靠指定context.Result完成断路器,有很多小细节:

    1. OnActionExecuting指定Result---Action不执行---其配套的Executed不执行—内层的ActionFilter也不执行--(内层都不执行了)—外层的ActionFilter可以完整执行---而且各种ResultFilter还是正常执行
    2. OnActionExecuted指定---只篡改结果,不影响任何执行(Json-Result—加个属性改个值)---而且ResultFilter还是正常执行----如果是需要做result断路或修改,推荐用这个
    3. OnActionExecutionAsync前半段指定, 不等价于OnActionExecuting指定Result,因为会直接抛异常,中断后续执行(包括resultfiler)—外层action还是执行,需要自行try catch
    4. OnActionExecutionAsync后半段指定,完全等价于OnActionExecuted指定Result,但是记得指定的是委托结果的Result
  • ActionFilter扩展

    最贴近方法,控制器也实例化了、参数也准备了,可以做更多事儿

    1. 参数获取:记录请求日志----参数通用校验(ModelState,基于特性封装)
    2. 参数修改:防SQL注入/CSRF----敏感词过滤/替换
    3. 结果指定: IP黑名单---反爬虫返回验证码---其实该在中间件完成
    4. 方法前后加入逻辑,可以做性能监控

    有参数,能修改参数;有结果,能修改结果, Everything is in control

    再说几个后面项目里面可能用到的扩展:

    1. 客户端缓存扩展---类似responsecache
    2. 跨域设置----------
    3. 2Commit防重复提交--------

    其实还可以延伸很多很多玩法,看场景需求,自己发散

  • 异常问题

    首先声明:本阶段的异常都会被ExceptionFilter抓取

    1. Action发生异常, OnActionExecuting和OnActionExecuted—都会正常执行---原因:自行try-catch保存到context
    2. OnActionExecuting发生异常---导致流程中断---Action不执行—Excuted也不执行(内层都不执行)—异常了---但是外层都是正常的(吞掉异常)
    3. OnActionExecuted发生异常---Action相关肯定都正常执行---但是会跳到异常处理去---result不执行, alwaysrun会执行

七、ResultFilter

1. ResultFilter演练

  1. 实现IResultFilter接口+特性子类
  2. CustomShowResultFilterAttribute实现
  3. CustomShowAsyncResultFilterAttribute实现
  4. 执行时机: Action执行后,然后OnResultExecuting和OnResultExecuted之间是ActionResult.ExecuteResult(cshtml执行或其他响应)
  5. 支持嵌套
  6. 支持控制器提供
  7. 基本流程验证

2. ResultFilter细节

关于断路问题:

  1. 已经到Result了,再指定Resut没意义了---很好懂
  2. 这里的断路应该是取消后续执行,包括 ResultExecutingContext.Cancel(取消后续执行)和ResultExecutedContext.Canceled

关于异常问题:

  1. ResultFilter--Result执行期间的异常, ExceptionFilter都抓不了
  2. ResultFilter的异常应该跟之前Action的异常差不多

AlwaysRunResultFilter?其实就是ResultFilter,只是额外来个接口标记

3. ResourceInvoker-ResultFilter

  1. ResultBegin
  2. ResultNext
  3. ResultAsyncBegin
  4. ResultAsyncEnd
  5. ResultSyncBegin
  6. ResultSyncEnd
  7. ResultInside
  8. ResultEnd

结果拦截器,已经熟门熟路了~

4. ResultFilter完整过程

跟Action几乎一抹一样,不记录那么多了

  1. 从ActionEnd进入,或者Exception完成后进入----进到ResultNext方法,从ResultBegin开始,先重置Filter集合的索引,然后去到ResultNext
  2. ResultNext也是3件事儿,异步ResultFilter去异步的;同步ResultFilter去同步的;都没有(也许是递归完了)则去ResultInside,里面是执行InvokeResultAsync(),把Result处理调用,最终去到ResultEnd;
  3. ResultSyncBegin会先OnResultExecuting,如果_resultExecutingContext.Cancel,则去到ResultEnd了;没有result,会执行InvokeNextResultFilterAsync(),递归本方法(ResourceInvoker的);递归完成后的流程还是走ResultSyncEnd

同步版本:

  1. ResultSyncEnd会执行OnResultExecuted,然后走向ResultEnd
  2. 异步版本跟这个差不多
  3. ResultEnd如果结果为空,会给个ResultExecutedContextSealed结果,然后结束,有递归结束递归,没有递归就结束本方法,回到最上面的ResultBegin;直到全部完成,流程结束;

5. 知识点

  • 断路器问题

    ResultFilter时, Context.Result已经赋值了,不存在指定这个短路了,如果要断路,其实就是取消后续执行? ResultExecutingContext.Cancel()

    1. 在Executing执行时,直接Cancel---中断后续流程(result—及filter—及Executed)-----嵌套的外层Filter不影响---检测Cancel—直接跳End—外层还可以(因为不检测)---结果就是产生一个200的响应,没有任何消息
    2. 在Executed执行,是指定Context.Canceled=true---一点影响都没有(完全不检测)
    3. Result在哪里的执行--ResultInside
  • Result异常问题

    从Action+ActionFilter结束后, Result+ResultFilter:

    1. 本阶段的异常会被ExceptionFilter抓取?! 不会的!不会的!不会的!
    2. View执行时发生异常, OnResultExecuting和OnResultExecuted还执行---当然可以---(外层都不受影响)-----
    3. OnResultExecuting发生异常---内层结束了(包括Executed)---外层都可以正常4
    4. OnResultExecuted发生异常---不影响页面展示—控制台有输出异常信息(各种rethrow)

6. Result期间的异常处理

  1. ExceptionFilter已经管不到Result,需要ResultFilter自行管理了
  2. OnResultExecuted可以获取全部异常信息(exception),处理异常(ExceptionHandled) ---记录日志等等
  3. 要处理异常,看图(ExceptionHandled = true,全局的地方来处理)

完整异常处理方案留待M-V-C流程后输出

八、AlwaysRunResultFilter

1. AlwaysRunResultFilter演练

  1. 实现IAlwaysRunResultFilter接口+特性子类
  2. CustomShowAlwaysRunResultFilterAttribute实现
  3. CustomShowAsyncAlwaysRunResultFilterAttribute实现
  4. 常规: ActionResult.ExecuteResult前后执行,跟ResultFilter一样的(本质就是ResultFilter)----一些特殊流程会不一样
  5. 特殊: OnAuthorization-Resource-Exception设置了Result执行断路后执行InvokeAlwaysRunResultFilters
  6. 支持嵌套
  7. 用ResultContext.Cancel()短路
  8. 基本流程验证

2. ResourceInvoker- AlwaysRunResultFilter

  1. ResultBegin
  2. ResultNext
  3. ResultAsyncBegin
  4. ResultAsyncEnd
  5. ResultSyncBegin
  6. ResultSyncEnd
  7. ResultInside
  8. ResultEnd

必运行AlwaysResultFilter,枚举-执行流程跟Result一致

3. AlwaysRunResultFilter-源码流程

跟ResultFilter几乎一样---因为就是一套代码,只是泛型不一样常规流程下,其实只要IResultFilter即可,因为AlwaysRun也是IResultFilter异常短路时,就走这个AlwaysRun---期间就有Result执行

4. 知识点

  • AlwaysRun执行时机

    授权失败、 Resource缓存命中,异常拦截、发生异常且处理后,会只走IAlwaysRunResultFilter,不走ResultFiler,调用InvokeAlwaysRunResultFilters

    分析找原因:

    1. 因为指定了新的Result,根本就不走Action-Result流程,所以普通的ResultFilter就没用了
    2. 但是新的Result也得转成输出,还是需要走Result流程,所以额外来个AlwaysRunResultFilter

    应用场景:

    1. 特点就是只要进入了FilterPipeline,无论是否断路,是一定会执行的ResultFilter---如果有些操作,希望即使断路,也能执行的,就写在这里面
    2. ResourceFilter命中缓存-----会直接执行Result,如果需要动态数据? 这时候没有Action了,可以通过AlwaysRunResultFilter的OnResultExecuting去获取动态数据加到Conetxt
    3. 即使被拦截-被异常,但是还可以记录个日志—统计个时间
    4. Result的Excuted异常处理,应该放在AlwaysRun这里
  • 断路器/异常

    这二者跟Result的一样,本质都是一个----唯一区别就是入口多点

    1. Canceled属性,有指定的----授权失败、 Resource缓存命中异常拦截、发生异常且处理后都指定了
    2. 异常处理没啥区别---唯一就是这个能保证执行,所以那个全局Result异常处理,应该用AlwaysRun