博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ASP.NET Core 2.2 : 十七.Action的执行(Endpoint.RequestDelegate后面的故事)(转)
阅读量:6180 次
发布时间:2019-06-21

本文共 9312 字,大约阅读时间需要 31 分钟。

上一章介绍了经过路由的处理,一个请求找到了具体处理这个请求的EndPoint,并最终执行它的RequestDelegate方法来处理这个Httpcontext。本章继续这个处理进程,按照惯例,依然通过几幅图来聊一聊这个RequestDelegate之后的故事。在此就避免不了的聊到各种Filter,它方便我们在action执行的前后做一些 “小动作”。()

一、概述

          首先看一下RequestDelegate这个方法:

RequestDelegate requestDelegate = (context) =>{    var routeData = context.GetRouteData();    var actionContext = new ActionContext(context, routeData, action);    var invoker = _invokerFactory.CreateInvoker(actionContext);    return invoker.InvokeAsync();};

          将这个方法的内容分为两部分:

          A. invoker的生成,前三句,通过CreateInvoker方法生成了一个invoker,它是一个比较复杂的综合体,包含了controller的创建工厂、参数的绑定方法以及本action相关的各种Filter的集合等, 也就是说它是前期准备工作阶段,这个阶段扩展开来涉及面比较广,在下文详细描述。  

          B.invoker的执行,最后一句,前面已经万事俱备,准备了好多方法,到此来执行。此时涉及到前面准备的各种方法的执行,各种Filter也在此时被执行。

二、invoker的生成

           依照习惯,还是通过流程图来看一看:

                                                                                                     图一()

          首先说一下此图的结构,每个泳道相当于是上一个泳道中的图标的细化说明,例如第二条泳道是图标①标识的方块的明细化。

          A. 泳道一:

                  就是第一节【概述】中描述的的内容,不再赘述。另外提一下本文的核心invoker本质上就是一个ControllerActionInvoker,也是图中的ActionInvokerProviderContext.Result。

          B.泳道二:即①的详细描述

          ① ActionInvokerFactory.CreateInvoker(actionContext)

1 public IActionInvoker CreateInvoker(ActionContext actionContext) 2 { 3     var context = new ActionInvokerProviderContext(actionContext); 4  5     foreach (var provider in _actionInvokerProviders) 6     { 7         provider.OnProvidersExecuting(context); 8     } 9 10     for (var i = _actionInvokerProviders.Length - 1; i >= 0; i--)11     {12         _actionInvokerProviders[i].OnProvidersExecuted(context);13     }14 15     return context.Result;16 }

          本章设计的这部分内容比较常见的一个操作就是context的封装,这从第一个泳道的第一个操作就开始了, 他将HttpContext、RouteData,ActionDescriptor封装到一起成了一个ActionContext ,而到了这个方法,又将这个ActionContext 封装成了ActionInvokerProviderContext,接下来就是遍历_actionInvokerProviders调用它们的OnProvidersExecuting和OnProvidersExecuted方法来设置ActionInvokerProviderContext.Result,也就是最终的ControllerActionInvoker。

          这里说一下_actionInvokerProviders,它的类型是IActionInvokerProvider[],默认情况下包含了两个,分别是ControllerActionInvokerProvider和PageActionInvokerProvider,第一个是用于MVC的action的处理,而第二个用于Razor Pages Web的处理。二者的OnProvidersExecuting方法都会首先判断当前action是不是自己对应的类型,若不是则直接跳过。二者的OnProvidersExecuted方法目前均为空。所以图中和下面关于OnProvidersExecuting的描述也仅限于ControllerActionInvokerProvider的OnProvidersExecuting方法。

          C.泳道三:ControllerActionInvokerProvider.OnProvidersExecuting(context)

          即泳道二中的③的详细描述

1 public void OnProvidersExecuting(ActionInvokerProviderContext context) 2 { 3     if (context.ActionContext.ActionDescriptor is ControllerActionDescriptor) 4     { 5         var controllerContext = new ControllerContext(context.ActionContext); 6         // PERF: These are rarely going to be changed, so let's go copy-on-write. 7         controllerContext.ValueProviderFactories = new CopyOnWriteList
(_valueProviderFactories); 8 controllerContext.ModelState.MaxAllowedErrors = _maxModelValidationErrors; 9 10 var cacheResult = _controllerActionInvokerCache.GetCachedResult(controllerContext);11 12 var invoker = new ControllerActionInvoker(13 _logger,14 _diagnosticSource,15 _mapper,16 controllerContext,17 cacheResult.cacheEntry,18 cacheResult.filters);19 20 context.Result = invoker;21 }22 }

         如上文所述,在处理之前,首先就是判断当前action是否是自己对应处理的类型。然后就是继续封装大法,将ActionContext封装成了ControllerContext。进而是调用GetCachedResult方法读取两个关键内容cacheResult.cacheEntry和cacheResult.filters后,将其封装成ControllerActionInvoker(⑤)。

          D.第四条泳道:

          对应的是第三条中的④ControllerActionInvokerCache.GetCachedResult(controllerContext);

1 public (ControllerActionInvokerCacheEntry cacheEntry, IFilterMetadata[] filters) GetCachedResult(ControllerContext controllerContext) 2 { 3     var cache = CurrentCache; 4     var actionDescriptor = controllerContext.ActionDescriptor; 5  6     IFilterMetadata[] filters; 7     if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry)) 8     { 9         var filterFactoryResult = FilterFactory.GetAllFilters(_filterProviders, controllerContext);10         filters = filterFactoryResult.Filters;11 12         var parameterDefaultValues = ParameterDefaultValues13             .GetParameterDefaultValues(actionDescriptor.MethodInfo);14 15         var objectMethodExecutor = ObjectMethodExecutor.Create(16             actionDescriptor.MethodInfo,17             actionDescriptor.ControllerTypeInfo,18             parameterDefaultValues);19 20         var controllerFactory = _controllerFactoryProvider.CreateControllerFactory(actionDescriptor);21         var controllerReleaser = _controllerFactoryProvider.CreateControllerReleaser(actionDescriptor);22         var propertyBinderFactory = ControllerBinderDelegateProvider.CreateBinderDelegate(23             _parameterBinder,24             _modelBinderFactory,25             _modelMetadataProvider,26             actionDescriptor,27             _mvcOptions);28 29         var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);30 31         cacheEntry = new ControllerActionInvokerCacheEntry(32             filterFactoryResult.CacheableFilters,33             controllerFactory,34             controllerReleaser,35             propertyBinderFactory,36             objectMethodExecutor,37             actionMethodExecutor);38         cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry);39     }40     else41     {42         // Filter instances from statically defined filter descriptors + from filter providers43         filters = FilterFactory.CreateUncachedFilters(_filterProviders, controllerContext, cacheEntry.CachedFilters);44     }45 46     return (cacheEntry, filters);

           总的来看,本段内容主要是为了组装cacheEntry和 filters两个内容,而一个大的 if 体现出这里加入了缓存机制,使系统不必每次都去拼凑这些,提高执行效率。

⑥IFilterMetadata[] filters,它是一个filter的集和,首先调用FilterFactory的GetAllFilters(_filterProviders, controllerContext)方法获取当前action对应的所有Filter并对这些Filter进行排序(Filter部分将在之后章节分享)。

接下来就是组装⑦cacheEntry,它的内容比较多,比较重要的几个有:⑧ controllerFactory和controllerReleaser他们的本质都是Func<ControllerContext, object>,也就是Controller的Create和Release方法。 ⑨propertyBinderFactory 是一个用于参数绑定的Task,可以说也是一个组装好准备被执行的方法。最后一个⑩actionMethodExecutor也就是执行者,通过ActionMethodExecutor.GetExecutor(objectMethodExecutor)方法从众多的action执行者(如图二)中找出一个当前action对应的执行者出来。

                                                             图二

总结: 本节invoker的生成,总的来说就是一个执行前“万事俱备”的过程,invoker是一个组装起来的集合,它包含一个人(执行者actionMethodExecutor)、N把枪(组装好用于“被执行”的方法例如controllerFactory、controllerReleaser和propertyBinderFactory,当然还有个filter的集和)。由此也可以进一步想到,接下来的过程就是这些准备好的内容按照一定的顺序逐步执行的过程。

 二、invoker的执行

invoker的执行也就是invoker.InvokeAsync(),虽然invoker本质上是ControllerActionInvoker,但这个方法写在ResourceInvoker类中, ControllerActionInvoker : ResourceInvoker, IActionInvoker 。

public virtual async Task InvokeAsync(){    try    {        await InvokeFilterPipelineAsync();    }    finally    {        ReleaseResources();        _logger.ExecutedAction(_actionContext.ActionDescriptor, stopwatch.GetElapsedTime());    }}private async Task InvokeFilterPipelineAsync(){    var next = State.InvokeBegin;    var scope = Scope.Invoker;    var state = (object)null;    // `isCompleted` will be set to true when we've reached a terminal state.    var isCompleted = false;    while (!isCompleted)    {        await Next(ref next, ref scope, ref state, ref isCompleted);    }}

看似比较简单的两个方法,从InvokeAsync方法中可以看出来,请求会进入筛选器管道进行处理,也就是 Task InvokeFilterPipelineAsync() 方法,借用官方文档中的一个图看一下

                                  图三

此图描述了请求经过其他中间件处理后,进入路由处理最终找到了对应的action,最终进入筛选器管道进行处理。而这个处理的核心部分就是方法中的 while (!isCompleted) 循环,它对应的Next方法比较长,如下(较长已折叠)

 
View Code

从代码可以看出,它是根据状态State进行轮转,而执行顺序是Authorization->Resource->Exception......  也就是说当前action对应的多种类型的Filter会按照这样的顺序被执行,如下图

            图四

可以看出,在上面几个Filter执行之后,ActionFilter的执行比较特殊,它将Action的执行包在了中间,这段逻辑写在了ControllerActionInvoker自己的类中,同样是一个 Task Next 方法被while循环调用,如下

 
View Code

而在ActionBegin的时候,通过ControllerFactory创建了Controller并调用 cacheEntry.ControllerBinderDelegate(_controllerContext, _instance, _arguments) 进行了参数绑定。

然后的顺序是   ActionFilter的OnActionExecuting方法 ->action的执行->ActionFilter的OnActionExecuted方法, action的执行如下:

private async Task InvokeActionMethodAsync(){    var controllerContext = _controllerContext;    var objectMethodExecutor = _cacheEntry.ObjectMethodExecutor;    var controller = _instance;    var arguments = _arguments;    var actionMethodExecutor = _cacheEntry.ActionMethodExecutor;    var orderedArguments = PrepareArguments(arguments, objectMethodExecutor);    var diagnosticSource = _diagnosticSource;    var logger = _logger;    IActionResult result = null;    try    {        diagnosticSource.BeforeActionMethod(            controllerContext,            arguments,            controller);        logger.ActionMethodExecuting(controllerContext, orderedArguments);        var stopwatch = ValueStopwatch.StartNew();        var actionResultValueTask = actionMethodExecutor.Execute(_mapper, objectMethodExecutor, controller, orderedArguments);        if (actionResultValueTask.IsCompletedSuccessfully)        {            result = actionResultValueTask.Result;        }        else        {            result = await actionResultValueTask;        }        _result = result;        logger.ActionMethodExecuted(controllerContext, result, stopwatch.GetElapsedTime());    }    finally    {        diagnosticSource.AfterActionMethod(            controllerContext,            arguments,            controllerContext,            result);    }}

 

总结: 如上文说的,本节的内容就是将准备阶段组装的多个方法在这里按一定的被逐步的执行(如图四)。

 

大概内容就是这样,详细分析起来涉及细节还有好多,后面的文章会对一些关键内容进行详细分享。

 

原文地址:https://www.cnblogs.com/FlyLolo/p/ASPNETCore2_17.html

转载于:https://www.cnblogs.com/NetPig/p/10671299.html

你可能感兴趣的文章
时间属性
查看>>
第十九章:集合视图(十七)
查看>>
BIOS
查看>>
Elasticsearch之元数据(meta-fields)介绍
查看>>
基于Django+Bootstrap框架,可视化展示内存监控信息
查看>>
Pytorch | BERT模型实现,提供转换脚本【横扫NLP】
查看>>
biostar handbook: 第七周笔记汇总+调整通知
查看>>
涨薪必备|给你一份超详细Spring Boot知识清单
查看>>
YII2 关联查询,不修改search, 使用 GridView::widget 输出
查看>>
DNS服务-了解篇
查看>>
Apache Shiro在web开发安全技术中的应用
查看>>
源码安装MySQL 5.1 GA
查看>>
苹果电脑获取Android Studio的发布版SHA1和开发版SHA1
查看>>
How to troubleshooting RAC Vip Problem
查看>>
jar 命令 打包装class文件的文件夹
查看>>
CentOS 7.2 部署Saltstack
查看>>
centos7下安装MPlayer
查看>>
docker容器中安装vim
查看>>
smokeping 监控
查看>>
NTB EEPROM设置与跨节点数据传输
查看>>