Spiga

ASP.NET Core 7 源码阅读7:CAR

2023-09-25 23:01:42

一、CAR

1. CAR本质

URL请求----调用Action----生成html响应(或其他响应)

  1. CAR就是个普通类---普通方法---最终生成HTML(也可能是其他的)
  2. 一个URL,怎么调用的Action---一定是反射----反射创建控制器实例,反射准备方法参数,反射调用Action方法(也许来点优化)
  3. 然后生成一段内容(啥都可以),写入到Response里面---然后由Kestrel回发到浏览器----浏览器解析报文做呈现

先Get这个本质,不要被各种封装各种特性看花眼了~

2. 关键节点-上帝视角

CAR虽然是最后才生效,但是在前面也出现了---全流程debug一下,回顾一下:

  1. 启动环节services.AddControllers注册IOC时扫描dll,保存到ApplicationManager
  2. 初始化中间件MapControllerRoute()转成ControllerActionDescripter
  3. 第一次请求进入,路由初始化DFAMatcher时触发转换成Endpoint,路由匹配找到Endpoint---鉴权授权---
  4. 执行Endpoint时实例化和缓存各种工厂、 Filter等---做好调用准备
  5. 执行Filter管道, ActionBegin状态实例化控制器, Bind参数
  6. ActionInside状态开始执行Action,得到ActionResult
  7. ResultInside状态开始执行ExecuteResult,得到结果
  8. Controller-Action-Result流程结束

3. CAR源码

按照主流程往后走,先关注Controller,再Action,再Result,其中会有回溯

  1. 步骤1: services.AddControllers注册时扫描dll,转成ApplciationPart,保存到ApplciationPartManager

    知识点:当前项目引用的dll都算数,所以是独立类库---扩展PartDemo

    1. 收集完的内容都是保存在ApplciationPartManager—等于是个中间层,其实就可以开个后门,通过其他方式添加数据进去---所以可以热插拔
    2. 甚至可以动态编译脚本---后插拔进去
  2. 步骤2:初始化中间件MapControllerRoute()注册UpdateCollection,注册UpdateEndPoint,触发ApplicationPart转成ControllerActionDescriptor

    知识点:

    1. 控制器规则, NoInheritController
    2. Property
    3. Action规则
    4. Parameter
    5. ModelConversion—之前扩展过

    还在启动环节---下面进入响应环节

  3. 步骤3: UpdateEndPoint(),路由匹配、鉴权授权等均不影响CAR了,路由匹配后Action执行前,在ControllerRequestDelegateFactory里面,要去实例化和缓存各种对象,准备开始执行FilterPipeline: 核心是准备Filter的实例 controllerFactory 控制器工厂 controllerReleaser控制器释放 propertyBinderFactory属性绑定工厂 objectMethodExecutor普通方法执行器 actionMethodExecutor方法Action执行器

    知识点:

    1. 各种Filter和FilterFactory、 IsResuable
    2. ControllerFactory和ControllerActivator
    3. propertyBinderFactory属性绑定工厂,后面要用的
    4. objectMethodExecutor是方法执行器,后面要用的
  4. 步骤4: ControllerActionInvoker.InvokeFilterPipeline, Invoke+Release,然后直到ActionBegin状态才实例化控制器,还是在_cacheEntry里面的ControllerFactory这个委托(或者是扩展的控制器工厂的方法)---

    知识点:

    1. 控制器实例化异常问题--在ExceptionFilter里面,所以能抓住
    2. 控制器对象生命周期问题:注意在InvokeFilterPipeline这个try下面就是finally,里面就要释放控制器实例的--意味着里面全部Filter+Action+Result都完成后释放----包括控制器里面注入的全部对象
  5. 步骤5: InvokeFilterPipeline到ActionBegin准备参数, BindArgument做的是匹配各种信息,因为这个时候已经知道Action,也有各种HttpContext---确实可以去获取参数

    知识点:这里很复杂,先理解下这个Action参数本质

    1. 当客户端浏览器发出一个请求的时候,有多个渠道携带参数: URL参数、 URL路由匹配出来的参数、请求Header参数、请求Body参数--
    2. 参数的类型也大不相同,可能是简单类型的参数,如字符串、整数或浮点数,也可能是复杂类型的参数,比如常见的Json、 XML等
    3. 然后这些是如何与目标Action的参数关联在一起并赋值的呢? ---细思恐极,头大

4. 控制器和EFCore生命周期问题

  1. EFCore7—在ASP.NET Core可以用Transient注册,但是一次注入2个DbContext,居然自动报错----新知识点----其实确实推荐EFCore的Context应该是Scoped
  2. 一种是控制器构造函数注入一系列对象A----VS----控制器只注册ServiceProvider,方法里面自行获取对应的实例B---VS---Action的参数注入,用【 FromService】 C

5. Controller建议

流程回顾:扫描dll加载识别---转Collection(规则)---准备工厂---开始创建(属性绑定)---FilterPipeline完成后释放

  1. 瘦控制器:控制器越简单越好,方便扩展---后端职责: 获取信息---校验信息---调用服务(InvokeBLL)---业务逻辑(写到Service)---数据访问---记录日志(AOP)---return
  2. 不建议刻意做属性注入、方法注入,也不建议去扩展ControllerFactory---真的需要,就替换IOC容器
  3. 控制器注入对象的3个用法:构造函数的方式最简单,但是会担心性能? 其实吧,浪费不大(1 用完其实都释放了 2 一般对象也不持有资源 3 一般构造也不慢)---所以常规上还是构造函数用的多-----如果持有资源/构造耗时长,希望快速释放,就该另外2种方式

二、Action

1. ActionArgument流程解析

框架是怎么实现,负责的Action参数绑定呢,分3个步骤:

  1. 准备Binder,在执行Filter管道之前---(实例化filter的时候)
  2. 执行Binder, ActionBegin的BindArgument()环节
  3. 执行Action前准备参数, ActionInside环节的PrepareArgument里面

再就是其中一些Binder的设计和源码---然后能扩展Binder

2. Argument流程

准备Binder,创建绑定方法发生在ControllerActionInvokerCache的GetCachedResult方法,已经为每个参数,都准备好binder

  1. 创建propertyBinderFactory,关键在ControllerBinderDelegateProvider.CreateBinderDelegate,里面有parameterBindingInfo 和propertyBindingInfo ,主要看前GetParameterBindingInfo,为每个参数绑定Binder

  2. ModelBinderFactory.CreateBinder,为每个参数匹配Binder---有缓存,看CreateBinderCoreUncached---其实就是遍历全部的IModelBinderProvider----其实是注入了MvcOptions的options.Value.ModelBinderProviders.ToArray()--是个BinderItem[],挨个儿检查哪个Binder是合法的,遇到第一个返回----外层是有循环参数逐一指定BinderItem

    扩展点:扩展那个ModelBinderProviders,并Add到MVCOptions去

  3. 准备Binder,创建绑定方法发生在ControllerActionInvokerCache的GetCachedResult方法,已经为每个参数,都准备好binder1 创建propertyBinderFactory,关键在ControllerBinderDelegateProvider.CreateBinderDelegate,里面有parameterBindingInfo 和propertyBindingInfo ,主要看前GetParameterBindingInfo,为每个参数绑定Binder

  4. ModelBinderFactory.CreateBinder,为每个参数匹配Binder---有缓存,看CreateBinderCoreUncached---其实就是遍历全部的IModelBinderProvider----其实是注入了MvcOptions的options.Value.ModelBinderProviders.ToArray()--是个BinderItem[],挨个儿检查哪个Binder是合法的,遇到第一个返回----外层是有循环参数逐一指定BinderItem扩展点:扩展那个ModelBinderProviders,并Add到MVCOptions去

备注: BindArgument后,完成了BindProperty

3. Binder解读

参数可能存在于URL、 Body、路由,甚至其他地方参数类型也大不相同, string、 int、自定义实体和集合

  1. 默认共18个Binder(绑定者),也就有18个ModelBinderProvider(判断是否使用)
  2. 执行ActionInvoker前就为每个参数找好Binder,其实是遍历ModelBinderProvider集合,用Provider做条件判断检测,找到第一个合适的Binder
  3. 这里还有逻辑优先顺序问题,毕竟可能满足多个Binder---数组的排列得是固定的

换言之---要记住18个规则以及顺序,确实不容易

4. 多Binder

  1. SimpleTypeModelBinderProvider, SimpleTypeModelBinder
  2. BodyModelBinderProvider, BodyModelBinder
  3. 来个顺序理解:简单类型+[FromBody]---必须Body在前
  4. ServiceModelBinderProvider, ServiceModelBinder
  5. And so on

因为规则太多-还有顺序要求,所以是真的记不住--只能靠经验+测试

5. 扩展Binder

下面来扩展一个大写string绑定

  1. 新增StringUpperModelBinderProvider
  2. 新增SimpleStringTrimModelBinder
  3. 注册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

  1. 发生在ControllerActionInvokerCache的GetCachedResult方法,为每个Action都准备好Executor---ObjectMethodExecutor的构造函数里面通过表达式目录树动态拼装了一个lambda委托+外层的缓存,起到反射优化的目的
  2. 在ControllerRequestDelegateFactory里面就准备了8个Executor,然后根据Action方法类型 返回值类型选择合适的
  3. 8种Executor,对应着不同的返回值,会有个结果包装---

第二步: 执行actionMethodExecutor

Filter管道执行到ActionInside时,里面执行InvokeActionMethod,根据对应的Executor来执行方法,这里直接调用前一步生成的委托,避免反射

知识点:基于表达式目录树动态生成委托---然后缓存起来了---Action第二次调用就不需要再拼装了—优化反射性能

7. Action流程总结

一波Action流程,最终目标就是生成ActionResult

  1. 关于Argument----准备Binder,从18种筛选---执行Binder, Prepare参数去执行Action
  2. 关于Action---准备Executor,利用了表达式动态拼装+缓存---8种Executor筛选合适的然后执行---得到不同的Result,准备执行Result

三、Result

1. Result执行

步骤8:

  1. Action+Exception都执行完了,然后开始ResultFilter系列
  2. 中间是ResultInside,会调用InvokeResultAsync()
  3. 这里就是Action执行后得到的结果,然后各种Result进行Execute(ViewResult、JsonResult等)

知识点:

  1. 理解Result的本质
  2. 看透各种Result操作,扩展Result
  3. 最复杂的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,所以看完整过程:

  1. 先看actionMethodExecutor, JsonResult得到的是SyncActionResultExecutor,然后在执行完Action后得到的是JsonResult
  2. 然后管道执行到ResultInside----InvokeResultAsync()----把ActionResult执行ExecuteResultAsync,跳到JsonResult类去了
  3. JsonResult里面ExecuteAsync获取的IActionResultExecutor,结果是默认的SystemTextJsonResultExecutor, ExecuteAsync就是把对象序列化成Json,写入response----其中还有些细节: (编码--content-type等),默认使用的JsonSerializer

4. 扩展JsonResult

序列化器的扩展,如何替换成用newtonsoft.Json?

  1. 修改注入的Executor---NewtonsoftJsonActionResultExecutor
  2. 定义新的JsonResult---NewtonsoftJsonResult

有JsonResult,是不是也可以有XmlResult? ExcelResult?

  1. XmlResult
  2. ExcelResult---也没区别,自己来。。

也就是已经可以任意定义序列化格式

5. Result源码之ObjectResult

ObjectResult和JsonResult在Action环节差不多,只是Action执行结果有区别:

  1. 先看actionMethodExecutor, ObjectResult得到的是SyncObjectResultExecutor,然后在执行完Action后得到结果,加了个Convert操作,变成的是ObjectResult(实现了ActionResult)---针对不同的object,统一加工统一处理
  2. 然后管道执行到ResultInside----InvokeResultAsync()----把ActionResult执行ExecuteResultAsync,跳到ObjectResult类去了
  3. 再看ObjectResult的执行,会找到ObjectResultExecutor---里面会先找结果的对象类型---然后关键是去到 DefaultOutputFormatterSelector.SelectFormatter去获取序列化格式的方式,该如何加工数据

这里有些麻烦,,因为到底返回个啥格式的数据呢? 还不知道呢

要看DefaultOutputFormatterSelector了

6. OutputFormatter源码

Action返回个Object,到底该返回啥格式的数据呢?靠OutputFormatter决定! DefaultOutputFormatterSelector里面一共有4个OutputFormatters,序列化格式数据来自IOptions options.Value.OutputFormatters,然后从中筛选合适的---- -包含两个条件

  1. SupportedMediaTypes:它是客户端在请求的时候给出的,标识客户端期望服务端按照什么样的格式返回请求结果---有就以此为准,没用上-可以不管
  2. Context.ContentType:它来自ObjectResult.ContentTypes,是由服务端在Action执行后的结果为准----遍历4个,分别执行CanWriteResult进行检测,找到第一个满足的就结束了----注意:按照固定顺序
  3. 最后执行Formatter完成格式转换即可

7. 扩展Formatter

想自定义序列化的格式,该如何处理?

  1. 扩展NameValueOutputFormatter,添加到MvcOptions去,可以自定义格式化
  2. 还有别的办法吗?去实现刚才这种自定义格式?自定义NameValueResult

8. Result源码之EmptyResult

VoidResult---EmptyResult不带返回值,其实也有个流程

  1. 先看actionMethodExecutor, JsonResult得到的是VoidResultExecutor,然后在执行完Action后没有结果,但是会返回个EmptyResult
  2. 然后管道执行到ResultInside----InvokeResultAsync()----把ActionResult执行ExecuteResultAsync,跳到EmptyResult类去了-----里面ExecuteAsync就是啥也没有,哪有啥意义?

其实可以自己写response!

9. 自定义响应结果

ASP.NET Core处理请求的本质,就是拼装最后结果,写入到HttpResponse

其他Result,等于是做了封装,

EmptyResult最直接,开发者你自己来

返回图片、验证码、 excel

10. 异步版本Result

所谓异步版本Result,就是Task这种,一般配套async一下其执行流程,其实跟同步的是几乎一样的:

  1. 先看actionMethodExecutor, Task得到的是TaskOfIActionResultExecutor,然后在执行完Action后,会多一个await动作,得到的就是JsonResult,
  2. 然后就没有然后了—都是一样的了

四、ViewResult

1. ViewResult上帝视角

ViewResult是最复杂的一个Result,涉及到View文件、生成html等,先来个上帝视角:

  1. 也是个IActionResult
  2. 在cshtml之前的流程是不变的,只是ViewResultExecutor处理复杂点
  3. ViewResultExecutor会按照一定规则去查找视图View
  4. 然后View在运行时其实是编译成了个类,继承自RazorPage,执行视图类的Execute(),拼装Html,写入Response

麻烦点:

  • 找视图,很多规则---可扩展
  • 视图和类之间到底是咋回事儿
  • Razor和Html的转换

2. ViewResult源码

常规Result流程,为View执行做好准备

  1. 先看actionMethodExecutor, ViewResult得到的是SyncActionResultExecutor,然后在执行完Action后得到的是ViewResult
  2. 然后管道执行到ResultInside----InvokeResultAsync()----把ActionResult执行ExecuteResultAsync,跳到ViewResult类去了
  3. ViewResult里面ExecuteAsync获取的IActionResultExecutor,结果是默认的ViewResultExecutor,其ExecuteAsync就是查找视图然后执行,写入响应

Debug查看,跟之前的几乎一样

ViewResultExecutor就是找视图,然后执行视图,得到html,写入response

ViewResultExecutor的执行,第一步是FindView()

  1. ViewResultExecutor.ExecuteAsync(),里面会先FindView()
  2. ViewEngine是空的,使用全局的默认引擎,其实是个集合ICompositeViewEngine--里面有个IList<>-----ViewName可以指定,否则用会获取ViewName,用路由匹配出来的Action的Name(约定俗成)---还有个ActionDescriptor里面的actionname,优先路由匹配结果的
  3. GetView()------遍历全部IViewEngine的GetView,默认是RazorViewEngine----失败了
  4. 再次调用CompositeViewEngine的FindView(),其实又是遍历的Engine集合,还是RazorViewEngine,这次可以找到------调用的是LocatePageFromViewLocations,找了控制器、Action的名字,然后去遍历RazorViewEngineOptions的ViewLocationExpanders,调用其PopulateValues方法填充路径---然后再调用OnCacheMiss时,执行ViewLocationExpanders的OnCacheMiss方法给出路径模板-----然后再分别填充上ViewName、 ControllerName等,得到完整路径5
  5. FindView()的下一步是CreateViewEngineResult-----先直接通过委托拿到了View的对象了,然后把各种信息打包成一个ViewEngineResult,FindView()结束

FindView:基于Action名字+控制器名字+约定速成的模板,找到完整路径—然后Razor实例化这个View类对象(ViewFactory委托做的)---扩展点就是路径模板

找到View之后,已经变成了一个类文件,然后直接执行View:

  1. cshtml已经编译成类Views_Home_Index,其基类是RazorView,开始执行ViewExecutor的ExecuteAsync,带释放
  2. 先指定Response的ResolveContentTypeAndEncoding---OnExecuting处理ViewData数据----准备Writer,执行RazorView的RenderAsync()---里面的RenderPageAsync--RenderPageCoreAsync里面最终就是page.ExecuteAsync(),拼装html,写入响应-------然后FlushAsync()

3. 扩展视图查找

视图的路径是框架内置的,写死的,也叫约定俗成,当然也是可以扩展的:

  1. 甚至可以直接扩展ViewResultExecutor-------可以,但是太难
  2. 可以扩展IViewEngine,覆写FindView方法--可以,但是太难
  3. 扩展CustomViewLocationExpander--Configure--视图配合---

扩展了默认的视图路径的查找规则,换模板路径一套后台,多套View,典型应用就是PC-移动端-----多语言其实这套方案意义不大:

  1. 真的写多套前端?维护成本挺高
  2. 现在更流行前后端分离,后端只管数据,不管html

除非特殊情况—或者又是后门

4. 关于View编译

  1. 视图文件本身就很奇怪, html和cs代码混编的---其实是RazorEngine定的规则,然后负责动态编译生成的---代码编译时,视图文件就已经编译到主dll里面----
  2. 视图文件是如何生成html? ---视图文件都变成类了,都是继承自RazorPage类,里面那个ExecuteAsync()方法就是负责生成html
  3. 关于动态编译---如果修改cshtml,就不会自动变化---但是有这个需求呀?添加.AddRazorRuntimeCompilation()就能动态编译-----但是反编译看dll是没有变化的, 只能猜测是运行时动态编译保存在内存然后覆盖了

5. Result流程总结

各种各样的Result都解析一遍了,其实还有FileResult等没讲,总结一下:

  1. 回归Result本质,就是把Action处理的数据,用合适的格式做序列化,然后写入Response---无论什么类型,本质都一样
  2. 大致流程:从ActionMethodExecutor开始,执行Action,然后将结果都包装成ActionResult---为了标准化-------然后就是执行Result,去到Result类,找ResultExecutor,负责具体执行----
  3. 扩展:就很多了。。。不同层次---真的看懂源码,理解流程,很多东西就可以为所欲为

五、回顾CAR流程

CAR: Controller-Action-Result,具体来说:

  1. 控制器实例化
  2. BindArgument,解析获取参数(BindProperty)
  3. PrepareArgument, ActionInvoke---有个标准化动作转成ActionResult
  4. ExecuteResult---分不同类型分别处理

1. 倒序回顾

  • FilterPipeline

    细致Filter流程时间点和CAR流程

    1. Middleware-In方向, EndPointMiddleware,实例化Filter,准备各种工厂和缓存
    2. 授权Filter
    3. 资源Filter
    4. 异常Filter
    5. ActionBegin,控制器实例化+ BindArgument
    6. ActionFilter开始
    7. PrepareArgument, ActionInvoke
    8. ActionFilter结束---- ResultFilter开始(没有异常Filter)
    9. 异常Filter结束------ ResultFilter开始
    10. Result执行
    11. ResultFilter结束
    12. 资源Filter-Executed结束
    13. Middleware-Out方向

    3个问题:授权、资源、异常Filter嵌套问题

    <FilterPipeline全流程详细描述图>

  • Filter嵌套

    Filter嵌套后,如果遇到断路,会带来不一样的流程:

    1. 授权Filter:无论是外层,还是里层的出现断路,都是没啥影响

    2. 资源Filter:中间层出现断路,会先AlwaysRun,再回去执行外层的excuted

    3. 异常Filter: 无论是否断路,多层Filter都执行完后才进去AlwaysRun—如果外层

      有资源Filter,则执行其Executed

  • 中间件流程

    中间件流程分两个阶段,请求响应时,启动初始化时

    1. 从全局异常处理开始ExceptionHandlerMiddlewareImpl—最入口的地方
    2. UseHsts、 HttpsRedirection等https相关
    3. StaticFileMiddleware静态文件(防盗链需要在这之前)
    4. 需要的话, Session在这里
    5. EndpointRoutingMiddleware路由匹配
    6. RateLimiterMiddleware(特性标记的需要这里,全局可以在前面)
    7. AuthenticationMiddleware鉴权+AuthorizationMiddleware授权
    8. EndpointMiddleware终结点处理路由

    中间件响应流程+FilterPipeline+CAR即为响应完整流程

    还要更细,其实在Kestrel监听的地方,也有一套Pipeline,最后一个环节是中间件流程

  • 启动流程

    启动环节,分3个大阶段:

    1. 启动的各种环境配置, IOC容器初始化,基础IOC注册-----MVC无关性
    2. AddMVC以及开发者的IOC注册---初始化管道模型Use+Build: UseRouting----MapControllerRoute---MVC密切相关
    3. Kestrel服务器完成Pipeline管道初始化和启动监听---纯纯的服务器

    启动+响应=整个源码世界

2. 核心传值对象

最核心的传值对象就是HttpContext

  1. 从哪里获取? 直接base----注入IHttpContextAccessor(需要先注册)
  2. Feature工具箱,里面东西特别,需要可以拿-----
  3. Item一般只是放个简单的值,类似开关、标识等等
  4. ConnectionInfo---IP和Port信息
  5. IServiceProvider---从HttpContext可以获取,就是IOC容器的实例,几乎所有的对象获取都是从这里的----
  6. Request+Response+Users+Session

3. 核心传值对象之元数据

  1. RouteData:路由匹配后的信息
  2. ActionDescriptor:详细的各种信息
  3. Endpoint:终结点
  4. endpoint.Metadata:各种的元数据

向页面传递的ViewBag ViewData TempData

4. 扩展套路

  1. 实现接口+IOC容器替换-builder.Services.Replace(ServiceDescriptor.Singleton<IA, A>());
  2. AOP扩展,各种Filter---各种Middleware---各种IOC容器的AOP扩展
  3. 工厂替换—LogFactory-IOCFactory-ControllerFactory-ServiceFilter
  4. Provider+Handler
  5. 特性标识---CustomHostingStartup—CustomStartupFilter—无侵入
  6. ModelConvention---通过模型约定去扩展
  7. 框架支持Options配置---MVCOptions+Add
  8. 扩展Handler---鉴权
  9. 扩展Requiredment---授权
  10. 框架内置集合里面去添加一个---NameFormatter

5. ASP.NET Core缓存

基于缓存理解ASP.NET Core项目的生命周期多级缓存,无处不在

客户端缓存、CDN、反向代理、本地缓存、分布式缓存

  • 客户端缓存

    css文件、 js文件、图片是如何缓存的? ----服务器响应时,通过ResponseHeader告知浏览器缓存起来,下次直接用---靠的是StaticfileMiddleware处理的时候写入的-之前讲过---简单演练:

    1. no-cache(不使用缓存)----不缓存,服务器请求数据—这时候ETag生效---304还是用本地的文件
    2. no-store(客户端不存储)---每次重新请求

    动态页面想缓存,哪有哪些方式可以实现呢?

    实现客户端缓存,就是指定ResponseHeader信息:前5都是基于程序代码完成

    1. Action直接写
    2. 框架自带ResponseCache这个Filter特性
    3. 自定义Filter----Action的前后 Result的前后 AlwaysRunResult前后
    4. 中间件可以写-GlobalClientCacheMiddleWare
    5. ModelConvertion---额外添加个Filter,跟3一样就行
    6. Nginx其实也可以添加自定义header
    7. 网关Ocelot可以添加
  • 反向代理缓存

    Nginx+ASP.NET Core

    反向代理缓存,浏览器每次都请求,但是被反向代理服务器拦截并响应

    请求根本就没进入应用程序

  • 页面静态化

    反向代理缓存,浏览器每次都请求,但是被反向代理服务器拦截并响应,请求根本就没进入应用程序,可以降低应用服务器的压力-----缓存文件是由Nginx管理,不太灵活

    其实还有一种可控类型的,叫静态化架构也是缓存---Nginx+ASP.NET Core中间件UseStaticPage

    静态化架构,也是一样,浏览器每次都请求,但是被反向代理服务器拦截并响应,请求根本就没进入应用程序,可以降低应用服务器的压力-----缓存文件是由程序管理,相对灵活,但稍显麻烦

  • 服务端缓存

    分不同层次不同方式:

    1. 中间件缓存---Action/Filter/View都不执行
    2. Filter缓存---ResourceFilter—Action不执行但AlwaysRunResult执行
    3. Action缓存---本地缓存+分布式缓存
    • 中间件缓存

      1. app.UseResponseCaching();
      2. builder.Services.AddResponseCaching();
      3. 配合ResponseCache的标记

      静态化+中间件判断文件存在和返回 请求是走到中间件层,然后被拦截返回 不会进入FilterPipeline

      其实用的不多

    • Filter缓存

      现在请求进入到FilterPipeline---缓存

      ResourceFilter实现缓存, 其实其他Filter也可以

      1. 会断路,不会执行Action动作
      2. 但是会执行AlwaysRun+ExecuteResult动作
    • Action缓存

      继续往前走,穿过Filter拦截,终于执行Action,当然也可以缓存,缓存具体的业务相关操作

      1. 本地缓存
      2. Redis分布式缓存
      3. AOP模式提供,封装到类库调用

贯穿了整个ASP.NET Core的网站的全周期:

  1. 客户端缓存,本质简单,所以在反向代理、中间件、 Filter、 Action都能添加
  2. 反向代理层:纯工具的+静态化(配合下程序)
  3. 服务端缓存:中间件缓存---Filter缓存---Action各种缓存---一是全周期各种不同的做法,也有不同的效果二是各种工具和框架代码的配合