ASP.NET Core 7 源码阅读7:CAR
2023-09-25 23:01:42一、CAR
1. CAR本质
URL请求----调用Action----生成html响应(或其他响应)
- CAR就是个普通类---普通方法---最终生成HTML(也可能是其他的)
- 一个URL,怎么调用的Action---一定是反射----反射创建控制器实例,反射准备方法参数,反射调用Action方法(也许来点优化)
- 然后生成一段内容(啥都可以),写入到Response里面---然后由Kestrel回发到浏览器----浏览器解析报文做呈现
先Get这个本质,不要被各种封装各种特性看花眼了~
2. 关键节点-上帝视角
CAR虽然是最后才生效,但是在前面也出现了---全流程debug一下,回顾一下:
- 启动环节services.AddControllers注册IOC时扫描dll,保存到ApplicationManager
- 初始化中间件MapControllerRoute()转成ControllerActionDescripter
- 第一次请求进入,路由初始化DFAMatcher时触发转换成Endpoint,路由匹配找到Endpoint---鉴权授权---
- 执行Endpoint时实例化和缓存各种工厂、 Filter等---做好调用准备
- 执行Filter管道, ActionBegin状态实例化控制器, Bind参数
- ActionInside状态开始执行Action,得到ActionResult
- ResultInside状态开始执行ExecuteResult,得到结果
- Controller-Action-Result流程结束
3. CAR源码
按照主流程往后走,先关注Controller,再Action,再Result,其中会有回溯
-
步骤1: services.AddControllers注册时扫描dll,转成ApplciationPart,保存到ApplciationPartManager
知识点:当前项目引用的dll都算数,所以是独立类库---扩展PartDemo
- 收集完的内容都是保存在ApplciationPartManager—等于是个中间层,其实就可以开个后门,通过其他方式添加数据进去---所以可以热插拔
- 甚至可以动态编译脚本---后插拔进去
-
步骤2:初始化中间件MapControllerRoute()注册UpdateCollection,注册UpdateEndPoint,触发ApplicationPart转成ControllerActionDescriptor
知识点:
- 控制器规则, NoInheritController
- Property
- Action规则
- Parameter
- ModelConversion—之前扩展过
还在启动环节---下面进入响应环节
-
步骤3: UpdateEndPoint(),路由匹配、鉴权授权等均不影响CAR了,路由匹配后Action执行前,在ControllerRequestDelegateFactory里面,要去实例化和缓存各种对象,准备开始执行FilterPipeline: 核心是准备Filter的实例 controllerFactory 控制器工厂 controllerReleaser控制器释放 propertyBinderFactory属性绑定工厂 objectMethodExecutor普通方法执行器 actionMethodExecutor方法Action执行器
知识点:
- 各种Filter和FilterFactory、 IsResuable
- ControllerFactory和ControllerActivator
- propertyBinderFactory属性绑定工厂,后面要用的
- objectMethodExecutor是方法执行器,后面要用的
-
步骤4: ControllerActionInvoker.InvokeFilterPipeline, Invoke+Release,然后直到ActionBegin状态才实例化控制器,还是在_cacheEntry里面的ControllerFactory这个委托(或者是扩展的控制器工厂的方法)---
知识点:
- 控制器实例化异常问题--在ExceptionFilter里面,所以能抓住
- 控制器对象生命周期问题:注意在InvokeFilterPipeline这个try下面就是finally,里面就要释放控制器实例的--意味着里面全部Filter+Action+Result都完成后释放----包括控制器里面注入的全部对象
-
步骤5: InvokeFilterPipeline到ActionBegin准备参数, BindArgument做的是匹配各种信息,因为这个时候已经知道Action,也有各种HttpContext---确实可以去获取参数
知识点:这里很复杂,先理解下这个Action参数本质
- 当客户端浏览器发出一个请求的时候,有多个渠道携带参数: URL参数、 URL路由匹配出来的参数、请求Header参数、请求Body参数--
- 参数的类型也大不相同,可能是简单类型的参数,如字符串、整数或浮点数,也可能是复杂类型的参数,比如常见的Json、 XML等
- 然后这些是如何与目标Action的参数关联在一起并赋值的呢? ---细思恐极,头大
4. 控制器和EFCore生命周期问题
- EFCore7—在ASP.NET Core可以用Transient注册,但是一次注入2个DbContext,居然自动报错----新知识点----其实确实推荐EFCore的Context应该是Scoped
- 一种是控制器构造函数注入一系列对象A----VS----控制器只注册ServiceProvider,方法里面自行获取对应的实例B---VS---Action的参数注入,用【 FromService】 C
5. Controller建议
流程回顾:扫描dll加载识别---转Collection(规则)---准备工厂---开始创建(属性绑定)---FilterPipeline完成后释放
- 瘦控制器:控制器越简单越好,方便扩展---后端职责: 获取信息---校验信息---调用服务(InvokeBLL)---业务逻辑(写到Service)---数据访问---记录日志(AOP)---return
- 不建议刻意做属性注入、方法注入,也不建议去扩展ControllerFactory---真的需要,就替换IOC容器
- 控制器注入对象的3个用法:构造函数的方式最简单,但是会担心性能? 其实吧,浪费不大(1 用完其实都释放了 2 一般对象也不持有资源 3 一般构造也不慢)---所以常规上还是构造函数用的多-----如果持有资源/构造耗时长,希望快速释放,就该另外2种方式
二、Action
1. ActionArgument流程解析
框架是怎么实现,负责的Action参数绑定呢,分3个步骤:
- 准备Binder,在执行Filter管道之前---(实例化filter的时候)
- 执行Binder, ActionBegin的BindArgument()环节
- 执行Action前准备参数, ActionInside环节的PrepareArgument里面
再就是其中一些Binder的设计和源码---然后能扩展Binder
2. Argument流程
准备Binder,创建绑定方法发生在ControllerActionInvokerCache的GetCachedResult方法,已经为每个参数,都准备好binder
-
创建propertyBinderFactory,关键在ControllerBinderDelegateProvider.CreateBinderDelegate,里面有parameterBindingInfo 和propertyBindingInfo ,主要看前GetParameterBindingInfo,为每个参数绑定Binder
-
ModelBinderFactory.CreateBinder,为每个参数匹配Binder---有缓存,看CreateBinderCoreUncached---其实就是遍历全部的IModelBinderProvider----其实是注入了MvcOptions的options.Value.ModelBinderProviders.ToArray()--是个BinderItem[],挨个儿检查哪个Binder是合法的,遇到第一个返回----外层是有循环参数逐一指定BinderItem
扩展点:扩展那个ModelBinderProviders,并Add到MVCOptions去
-
准备Binder,创建绑定方法发生在ControllerActionInvokerCache的GetCachedResult方法,已经为每个参数,都准备好binder1 创建propertyBinderFactory,关键在ControllerBinderDelegateProvider.CreateBinderDelegate,里面有parameterBindingInfo 和propertyBindingInfo ,主要看前GetParameterBindingInfo,为每个参数绑定Binder
-
ModelBinderFactory.CreateBinder,为每个参数匹配Binder---有缓存,看CreateBinderCoreUncached---其实就是遍历全部的IModelBinderProvider----其实是注入了MvcOptions的options.Value.ModelBinderProviders.ToArray()--是个BinderItem[],挨个儿检查哪个Binder是合法的,遇到第一个返回----外层是有循环参数逐一指定BinderItem扩展点:扩展那个ModelBinderProviders,并Add到MVCOptions去
备注: BindArgument后,完成了BindProperty
3. Binder解读
参数可能存在于URL、 Body、路由,甚至其他地方参数类型也大不相同, string、 int、自定义实体和集合
- 默认共18个Binder(绑定者),也就有18个ModelBinderProvider(判断是否使用)
- 执行ActionInvoker前就为每个参数找好Binder,其实是遍历ModelBinderProvider集合,用Provider做条件判断检测,找到第一个合适的Binder
- 这里还有逻辑优先顺序问题,毕竟可能满足多个Binder---数组的排列得是固定的
换言之---要记住18个规则以及顺序,确实不容易
4. 多Binder
- SimpleTypeModelBinderProvider, SimpleTypeModelBinder
- BodyModelBinderProvider, BodyModelBinder
- 来个顺序理解:简单类型+[FromBody]---必须Body在前
- ServiceModelBinderProvider, ServiceModelBinder
- And so on
因为规则太多-还有顺序要求,所以是真的记不住--只能靠经验+测试
5. 扩展Binder
下面来扩展一个大写string绑定
- 新增StringUpperModelBinderProvider
- 新增SimpleStringTrimModelBinder
- 注册IOC时给MVCOption的ModelBinderProviders添加到索引0—顺序
一方面是匹配---另外是转换参数(如果要修改参数,不如等到ActionFilter)
还有复杂的,见官方文档
https://docs.microsoft.com/zh-cn/aspnet/core/mvc/advanced/custom-modelbinding?view=aspnetcore-7.0
6. MethodExecutor流程
步骤6: InvokeActionMethodAsync()开始执行Action得到Result(ActionMethodExecutor)----然后Action系列Filter结束
又分2个地方: 1是ControllerActionInvokerCache的GetCachedResult 2是ActionInside环节去执行
知识点:框架需要处理的Action分8种情况,于是就定义了8个actionMethodExecutor,有啥区别,如何调用?
第一步: 创建ActionMethodExecutor
- 发生在ControllerActionInvokerCache的GetCachedResult方法,为每个Action都准备好Executor---ObjectMethodExecutor的构造函数里面通过表达式目录树动态拼装了一个lambda委托+外层的缓存,起到反射优化的目的
- 在ControllerRequestDelegateFactory里面就准备了8个Executor,然后根据Action方法类型 返回值类型选择合适的
- 8种Executor,对应着不同的返回值,会有个结果包装---
第二步: 执行actionMethodExecutor
Filter管道执行到ActionInside时,里面执行InvokeActionMethod,根据对应的Executor来执行方法,这里直接调用前一步生成的委托,避免反射
知识点:基于表达式目录树动态生成委托---然后缓存起来了---Action第二次调用就不需要再拼装了—优化反射性能
7. Action流程总结
一波Action流程,最终目标就是生成ActionResult
- 关于Argument----准备Binder,从18种筛选---执行Binder, Prepare参数去执行Action
- 关于Action---准备Executor,利用了表达式动态拼装+缓存---8种Executor筛选合适的然后执行---得到不同的Result,准备执行Result
三、Result
1. Result执行
步骤8:
- Action+Exception都执行完了,然后开始ResultFilter系列
- 中间是ResultInside,会调用InvokeResultAsync()
- 这里就是Action执行后得到的结果,然后各种Result进行Execute(ViewResult、JsonResult等)
知识点:
- 理解Result的本质
- 看透各种Result操作,扩展Result
- 最复杂的ViewResult解析和扩展
2. Result本质
Http请求最终的结果,就是服务端kestrel向浏览器返回http协议的文本文本可能是各种内容, Html—Json—string---图片---Excel等等到Result最后一步了,其实就是拼装最后结果,写入到HttpResponse,
Result的最终只是对外输出---页面Html—Json—string---图片/Excel----设置ContentType、 Stream、 Header、 ContentLength等
因此会有多种ActionResult来执行,其流程略有差异,下面依次解读:JsonResult、ObjectResult、EmptyResult、ViewResult
3. Result源码之JsonResult
先从JsonResult开始,又常见又简单,第一个Result,所以看完整过程:
- 先看actionMethodExecutor, JsonResult得到的是SyncActionResultExecutor,然后在执行完Action后得到的是JsonResult
- 然后管道执行到ResultInside----InvokeResultAsync()----把ActionResult执行ExecuteResultAsync,跳到JsonResult类去了
- JsonResult里面ExecuteAsync获取的IActionResultExecutor
,结果是默认的SystemTextJsonResultExecutor, ExecuteAsync就是把对象序列化成Json,写入response----其中还有些细节: (编码--content-type等),默认使用的JsonSerializer
4. 扩展JsonResult
序列化器的扩展,如何替换成用newtonsoft.Json?
- 修改注入的Executor---NewtonsoftJsonActionResultExecutor
- 定义新的JsonResult---NewtonsoftJsonResult
有JsonResult,是不是也可以有XmlResult? ExcelResult?
- XmlResult
- ExcelResult---也没区别,自己来。。
也就是已经可以任意定义序列化格式
5. Result源码之ObjectResult
ObjectResult和JsonResult在Action环节差不多,只是Action执行结果有区别:
- 先看actionMethodExecutor, ObjectResult得到的是SyncObjectResultExecutor,然后在执行完Action后得到结果,加了个Convert操作,变成的是ObjectResult(实现了ActionResult)---针对不同的object,统一加工统一处理
- 然后管道执行到ResultInside----InvokeResultAsync()----把ActionResult执行ExecuteResultAsync,跳到ObjectResult类去了
- 再看ObjectResult的执行,会找到ObjectResultExecutor---里面会先找结果的对象类型---然后关键是去到 DefaultOutputFormatterSelector.SelectFormatter去获取序列化格式的方式,该如何加工数据
这里有些麻烦,,因为到底返回个啥格式的数据呢? 还不知道呢
要看DefaultOutputFormatterSelector了
6. OutputFormatter源码
Action返回个Object,到底该返回啥格式的数据呢?靠OutputFormatter决定! DefaultOutputFormatterSelector里面一共有4个OutputFormatters,序列化格式数据来自IOptions
- SupportedMediaTypes:它是客户端在请求的时候给出的,标识客户端期望服务端按照什么样的格式返回请求结果---有就以此为准,没用上-可以不管
- Context.ContentType:它来自ObjectResult.ContentTypes,是由服务端在Action执行后的结果为准----遍历4个,分别执行CanWriteResult进行检测,找到第一个满足的就结束了----注意:按照固定顺序
- 最后执行Formatter完成格式转换即可
7. 扩展Formatter
想自定义序列化的格式,该如何处理?
- 扩展NameValueOutputFormatter,添加到MvcOptions去,可以自定义格式化
- 还有别的办法吗?去实现刚才这种自定义格式?自定义NameValueResult
8. Result源码之EmptyResult
VoidResult---EmptyResult不带返回值,其实也有个流程
- 先看actionMethodExecutor, JsonResult得到的是VoidResultExecutor,然后在执行完Action后没有结果,但是会返回个EmptyResult
- 然后管道执行到ResultInside----InvokeResultAsync()----把ActionResult执行ExecuteResultAsync,跳到EmptyResult类去了-----里面ExecuteAsync就是啥也没有,哪有啥意义?
其实可以自己写response!
9. 自定义响应结果
ASP.NET Core处理请求的本质,就是拼装最后结果,写入到HttpResponse
其他Result,等于是做了封装,
EmptyResult最直接,开发者你自己来
返回图片、验证码、 excel
10. 异步版本Result
所谓异步版本Result,就是Task
- 先看actionMethodExecutor, Task
得到的是TaskOfIActionResultExecutor,然后在执行完Action后,会多一个await动作,得到的就是JsonResult, - 然后就没有然后了—都是一样的了
四、ViewResult
1. ViewResult上帝视角
ViewResult是最复杂的一个Result,涉及到View文件、生成html等,先来个上帝视角:
- 也是个IActionResult
- 在cshtml之前的流程是不变的,只是ViewResultExecutor处理复杂点
- ViewResultExecutor会按照一定规则去查找视图View
- 然后View在运行时其实是编译成了个类,继承自RazorPage,执行视图类的Execute(),拼装Html,写入Response
麻烦点:
- 找视图,很多规则---可扩展
- 视图和类之间到底是咋回事儿
- Razor和Html的转换
2. ViewResult源码
常规Result流程,为View执行做好准备
- 先看actionMethodExecutor, ViewResult得到的是SyncActionResultExecutor,然后在执行完Action后得到的是ViewResult
- 然后管道执行到ResultInside----InvokeResultAsync()----把ActionResult执行ExecuteResultAsync,跳到ViewResult类去了
- ViewResult里面ExecuteAsync获取的IActionResultExecutor
,结果是默认的ViewResultExecutor,其ExecuteAsync就是查找视图然后执行,写入响应
Debug查看,跟之前的几乎一样
ViewResultExecutor就是找视图,然后执行视图,得到html,写入response
ViewResultExecutor的执行,第一步是FindView()
- ViewResultExecutor.ExecuteAsync(),里面会先FindView()
- ViewEngine是空的,使用全局的默认引擎,其实是个集合ICompositeViewEngine--里面有个IList<>-----ViewName可以指定,否则用会获取ViewName,用路由匹配出来的Action的Name(约定俗成)---还有个ActionDescriptor里面的actionname,优先路由匹配结果的
- GetView()------遍历全部IViewEngine的GetView,默认是RazorViewEngine----失败了
- 再次调用CompositeViewEngine的FindView(),其实又是遍历的Engine集合,还是RazorViewEngine,这次可以找到------调用的是LocatePageFromViewLocations,找了控制器、Action的名字,然后去遍历RazorViewEngineOptions的ViewLocationExpanders,调用其PopulateValues方法填充路径---然后再调用OnCacheMiss时,执行ViewLocationExpanders的OnCacheMiss方法给出路径模板-----然后再分别填充上ViewName、 ControllerName等,得到完整路径5
- FindView()的下一步是CreateViewEngineResult-----先直接通过委托拿到了View的对象了,然后把各种信息打包成一个ViewEngineResult,FindView()结束
FindView:基于Action名字+控制器名字+约定速成的模板,找到完整路径—然后Razor实例化这个View类对象(ViewFactory委托做的)---扩展点就是路径模板
找到View之后,已经变成了一个类文件,然后直接执行View:
- cshtml已经编译成类Views_Home_Index,其基类是RazorView,开始执行ViewExecutor的ExecuteAsync,带释放
- 先指定Response的ResolveContentTypeAndEncoding---OnExecuting处理ViewData数据----准备Writer,执行RazorView的RenderAsync()---里面的RenderPageAsync--RenderPageCoreAsync里面最终就是page.ExecuteAsync(),拼装html,写入响应-------然后FlushAsync()
3. 扩展视图查找
视图的路径是框架内置的,写死的,也叫约定俗成,当然也是可以扩展的:
- 甚至可以直接扩展ViewResultExecutor-------可以,但是太难
- 可以扩展IViewEngine,覆写FindView方法--可以,但是太难
- 扩展CustomViewLocationExpander--Configure
--视图配合---
扩展了默认的视图路径的查找规则,换模板路径一套后台,多套View,典型应用就是PC-移动端-----多语言其实这套方案意义不大:
- 真的写多套前端?维护成本挺高
- 现在更流行前后端分离,后端只管数据,不管html
除非特殊情况—或者又是后门
4. 关于View编译
- 视图文件本身就很奇怪, html和cs代码混编的---其实是RazorEngine定的规则,然后负责动态编译生成的---代码编译时,视图文件就已经编译到主dll里面----
- 视图文件是如何生成html? ---视图文件都变成类了,都是继承自RazorPage类,里面那个ExecuteAsync()方法就是负责生成html
- 关于动态编译---如果修改cshtml,就不会自动变化---但是有这个需求呀?添加.AddRazorRuntimeCompilation()就能动态编译-----但是反编译看dll是没有变化的, 只能猜测是运行时动态编译保存在内存然后覆盖了
5. Result流程总结
各种各样的Result都解析一遍了,其实还有FileResult等没讲,总结一下:
- 回归Result本质,就是把Action处理的数据,用合适的格式做序列化,然后写入Response---无论什么类型,本质都一样
- 大致流程:从ActionMethodExecutor开始,执行Action,然后将结果都包装成ActionResult---为了标准化-------然后就是执行Result,去到Result类,找ResultExecutor,负责具体执行----
- 扩展:就很多了。。。不同层次---真的看懂源码,理解流程,很多东西就可以为所欲为
五、回顾CAR流程
CAR: Controller-Action-Result,具体来说:
- 控制器实例化
- BindArgument,解析获取参数(BindProperty)
- PrepareArgument, ActionInvoke---有个标准化动作转成ActionResult
- ExecuteResult---分不同类型分别处理
1. 倒序回顾
-
FilterPipeline
细致Filter流程时间点和CAR流程
- Middleware-In方向, EndPointMiddleware,实例化Filter,准备各种工厂和缓存
- 授权Filter
- 资源Filter
- 异常Filter
- ActionBegin,控制器实例化+ BindArgument
- ActionFilter开始
- PrepareArgument, ActionInvoke
- ActionFilter结束---- ResultFilter开始(没有异常Filter)
- 异常Filter结束------ ResultFilter开始
- Result执行
- ResultFilter结束
- 资源Filter-Executed结束
- Middleware-Out方向
3个问题:授权、资源、异常Filter嵌套问题
<FilterPipeline全流程详细描述图>
-
Filter嵌套
Filter嵌套后,如果遇到断路,会带来不一样的流程:
-
授权Filter:无论是外层,还是里层的出现断路,都是没啥影响
-
资源Filter:中间层出现断路,会先AlwaysRun,再回去执行外层的excuted
-
异常Filter: 无论是否断路,多层Filter都执行完后才进去AlwaysRun—如果外层
有资源Filter,则执行其Executed
-
-
中间件流程
中间件流程分两个阶段,请求响应时,启动初始化时
- 从全局异常处理开始ExceptionHandlerMiddlewareImpl—最入口的地方
- UseHsts、 HttpsRedirection等https相关
- StaticFileMiddleware静态文件(防盗链需要在这之前)
- 需要的话, Session在这里
- EndpointRoutingMiddleware路由匹配
- RateLimiterMiddleware(特性标记的需要这里,全局可以在前面)
- AuthenticationMiddleware鉴权+AuthorizationMiddleware授权
- EndpointMiddleware终结点处理路由
中间件响应流程+FilterPipeline+CAR即为响应完整流程
还要更细,其实在Kestrel监听的地方,也有一套Pipeline,最后一个环节是中间件流程
-
启动流程
启动环节,分3个大阶段:
- 启动的各种环境配置, IOC容器初始化,基础IOC注册-----MVC无关性
- AddMVC以及开发者的IOC注册---初始化管道模型Use+Build: UseRouting----MapControllerRoute---MVC密切相关
- Kestrel服务器完成Pipeline管道初始化和启动监听---纯纯的服务器
启动+响应=整个源码世界
2. 核心传值对象
最核心的传值对象就是HttpContext
- 从哪里获取? 直接base----注入IHttpContextAccessor(需要先注册)
- Feature工具箱,里面东西特别,需要可以拿-----
- Item一般只是放个简单的值,类似开关、标识等等
- ConnectionInfo---IP和Port信息
- IServiceProvider---从HttpContext可以获取,就是IOC容器的实例,几乎所有的对象获取都是从这里的----
- Request+Response+Users+Session
3. 核心传值对象之元数据
- RouteData:路由匹配后的信息
- ActionDescriptor:详细的各种信息
- Endpoint:终结点
- endpoint.Metadata:各种的元数据
向页面传递的ViewBag ViewData TempData
4. 扩展套路
- 实现接口+IOC容器替换-builder.Services.Replace(ServiceDescriptor.Singleton<IA, A>());
- AOP扩展,各种Filter---各种Middleware---各种IOC容器的AOP扩展
- 工厂替换—LogFactory-IOCFactory-ControllerFactory-ServiceFilter
- Provider+Handler
- 特性标识---CustomHostingStartup—CustomStartupFilter—无侵入
- ModelConvention---通过模型约定去扩展
- 框架支持Options配置---MVCOptions+Add
- 扩展Handler---鉴权
- 扩展Requiredment---授权
- 框架内置集合里面去添加一个---NameFormatter
5. ASP.NET Core缓存
基于缓存理解ASP.NET Core项目的生命周期多级缓存,无处不在
客户端缓存、CDN、反向代理、本地缓存、分布式缓存
-
客户端缓存
css文件、 js文件、图片是如何缓存的? ----服务器响应时,通过ResponseHeader告知浏览器缓存起来,下次直接用---靠的是StaticfileMiddleware处理的时候写入的-之前讲过---简单演练:
- no-cache(不使用缓存)----不缓存,服务器请求数据—这时候ETag生效---304还是用本地的文件
- no-store(客户端不存储)---每次重新请求
动态页面想缓存,哪有哪些方式可以实现呢?
实现客户端缓存,就是指定ResponseHeader信息:前5都是基于程序代码完成
- Action直接写
- 框架自带ResponseCache这个Filter特性
- 自定义Filter----Action的前后 Result的前后 AlwaysRunResult前后
- 中间件可以写-GlobalClientCacheMiddleWare
- ModelConvertion---额外添加个Filter,跟3一样就行
- Nginx其实也可以添加自定义header
- 网关Ocelot可以添加
-
反向代理缓存
Nginx+ASP.NET Core
反向代理缓存,浏览器每次都请求,但是被反向代理服务器拦截并响应
请求根本就没进入应用程序
-
页面静态化
反向代理缓存,浏览器每次都请求,但是被反向代理服务器拦截并响应,请求根本就没进入应用程序,可以降低应用服务器的压力-----缓存文件是由Nginx管理,不太灵活
其实还有一种可控类型的,叫静态化架构也是缓存---Nginx+ASP.NET Core中间件UseStaticPage
静态化架构,也是一样,浏览器每次都请求,但是被反向代理服务器拦截并响应,请求根本就没进入应用程序,可以降低应用服务器的压力-----缓存文件是由程序管理,相对灵活,但稍显麻烦
-
服务端缓存
分不同层次不同方式:
- 中间件缓存---Action/Filter/View都不执行
- Filter缓存---ResourceFilter—Action不执行但AlwaysRunResult执行
- Action缓存---本地缓存+分布式缓存
-
中间件缓存
- app.UseResponseCaching();
- builder.Services.AddResponseCaching();
- 配合ResponseCache的标记
静态化+中间件判断文件存在和返回 请求是走到中间件层,然后被拦截返回 不会进入FilterPipeline
其实用的不多
-
Filter缓存
现在请求进入到FilterPipeline---缓存
ResourceFilter实现缓存, 其实其他Filter也可以
- 会断路,不会执行Action动作
- 但是会执行AlwaysRun+ExecuteResult动作
-
Action缓存
继续往前走,穿过Filter拦截,终于执行Action,当然也可以缓存,缓存具体的业务相关操作
- 本地缓存
- Redis分布式缓存
- AOP模式提供,封装到类库调用
贯穿了整个ASP.NET Core的网站的全周期:
- 客户端缓存,本质简单,所以在反向代理、中间件、 Filter、 Action都能添加
- 反向代理层:纯工具的+静态化(配合下程序)
- 服务端缓存:中间件缓存---Filter缓存---Action各种缓存---一是全周期各种不同的做法,也有不同的效果二是各种工具和框架代码的配合