asp.Netcore mvcプロファイリング:動作実行

11672 ワード

前の文章に続く.ルーティングとアクションが一致すると、最終的に現在のリクエストと最も一致するActionDescriptorが得られ、IActionInvokerでアクションが実行されます.
まず、IActionInvokerがどのように入手できるかを見てみましょう.コードは以下の通りです.
context.Handler = (c) =>
            {
                var routeData = c.GetRouteData();
                //  actiondescriptor   ActionContext  
                var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
                if (_actionContextAccessor != null)
                {
                    _actionContextAccessor.ActionContext = actionContext;
                }
                //  IActionInvoker
                var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
                if (invoker == null)
                {
                    throw new InvalidOperationException(
                        Resources.FormatActionInvokerFactory_CouldNotCreateInvoker(
                            actionDescriptor.DisplayName));
                }
                //  invoker    
                return invoker.InvokeAsync();
            };

上記のコードから分かるように、1つのIActionInvokerはIActionInvokerFactoryによって作成され、フレームワーク内のインタフェース実装クラスはActionInvokerFactoryであり、このクラスCreateInvokerメソッド実装コードは以下の通りである.
public IActionInvoker CreateInvoker(ActionContext actionContext)
        {
            var context = new ActionInvokerProviderContext(actionContext);

            foreach (var provider in _actionInvokerProviders)
            {
                provider.OnProvidersExecuting(context);
            }

            for (var i = _actionInvokerProviders.Length - 1; i >= 0; i--)
            {
                _actionInvokerProviders[i].OnProvidersExecuted(context);
            }

            return context.Result;
        }

方法では、まずActionInvokerProviderContextをインスタンス化し、IActionInvokerProviderを呼び出してcontextを設定.Result,context.ResultはIActionInvokerなので、フレームワークのIActionInvokerProvider実装クラスを追跡し、その中でどのように動作しているかを見てみましょう.フレームワークには、次のようなIActionInvokerオブジェクトがOnProvidersExecutingメソッドで作成されたインプリメンテーションクラスが用意されています.
public void OnProvidersExecuting(ActionInvokerProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            var actionDescriptor = context.ActionContext.ActionDescriptor as ControllerActionDescriptor;

            if (actionDescriptor != null)
            {
                var controllerContext = new ControllerContext(context.ActionContext);
                // PERF: These are rarely going to be changed, so let's go copy-on-write.
                controllerContext.ValueProviderFactories = new CopyOnWriteList(_valueProviderFactories);
                controllerContext.ModelState.MaxAllowedErrors = _maxModelValidationErrors;
                
                var cacheState = _controllerActionInvokerCache.GetState(controllerContext);

                context.Result = new ControllerActionInvoker(
                    _controllerFactory,
                    _argumentBinder,
                    _logger,
                    _diagnosticSource,
                    controllerContext,
                    cacheState.Filters,
                    cacheState.ActionMethodExecutor);
            }
        }

上のコードから分かるように、最終的には、現在の動作に関連付けられたフィルタ情報の集合を表すIFIlterMetadExecutorという最後の2つのパラメータに重点を置いたControllerActionInvokerオブジェクトがインスタンス化されています.これがコントローラメソッドのアクチュエータです.この2つのパラメータはcacheStateから来て、このオブジェクトは呼び出しによって_controllerActionInvokerCache.コントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラコントローラ
 public struct ControllerActionInvokerState
        {
            public ControllerActionInvokerState(
                IFilterMetadata[] filters,
                ObjectMethodExecutor actionMethodExecutor)
            {
                Filters = filters;
                ActionMethodExecutor = actionMethodExecutor;
            }
            //       
            public IFilterMetadata[] Filters { get; }
            //     
            public ObjectMethodExecutor ActionMethodExecutor { get; set; }
        }

CacheStateの作成手順は次のとおりです.
 public ControllerActionInvokerState GetState(ControllerContext controllerContext)
        {
            var cache = CurrentCache;
            var actionDescriptor = controllerContext.ActionDescriptor;

            IFilterMetadata[] filters;
            Entry cacheEntry;
            if (!cache.Entries.TryGetValue(actionDescriptor, out cacheEntry))
            {
                var filterFactoryResult = FilterFactory.GetAllFilters(_filterProviders, controllerContext);
                filters = filterFactoryResult.Filters;

                var executor = ObjectMethodExecutor.Create(
                    actionDescriptor.MethodInfo,
                    actionDescriptor.ControllerTypeInfo);

                cacheEntry = new Entry(filterFactoryResult.CacheableFilters, executor);
                cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry);
            }
            else
            {
                // Filter instances from statically defined filter descriptors + from filter providers
                filters = FilterFactory.CreateUncachedFilters(_filterProviders, controllerContext, cacheEntry.FilterItems);
            }

            return new ControllerActionInvokerState(filters, cacheEntry.ActionMethodExecutor);
        }

主に10行目から15行目のコードを見てみましょう.まずFilterFactoryにより現在の動作に関連するフィルタ情報の集合を取得し、次にObjectMethodExecutorを通過する.CreateはObjectMethodExecutorオブジェクトを作成し、作成してキャッシュします.
実はここまでIActionInvokderがControllerActionInvokerであることを知っていて、そのオブジェクトのInvokeAsyncメソッドを呼び出して動作実行を開始します.最新バージョンの実装ではステートマシンが採用されています(概念的にエラーが発生した場合は、レンガを叩いて訂正することを歓迎する)の特徴は、実行時に異なる状態の間で切り替え、最終的に処理を完了することである.この部分の作業は、Nextメソッドで実現される.メソッドが実際にState.ActionInside状態を実行する場合に実行され、この状態でInvokeActionMethodAsyncを呼び出してコントローラメソッドの実行を完了する.主なコードは以下の通りである.
                //                  ,       
                var returnType = executor.MethodReturnType;
                if (returnType == typeof(void))
                {
                    executor.Execute(controller, orderedArguments);
                    result = new EmptyResult();
                }
                else if (returnType == typeof(Task))
                {
                    await (Task)executor.Execute(controller, orderedArguments);
                    result = new EmptyResult();
                }
                else if (executor.TaskGenericType == typeof(IActionResult))
                {
                    result = await (Task)executor.Execute(controller, orderedArguments);
                    if (result == null)
                    {
                        throw new InvalidOperationException(
                            Resources.FormatActionResult_ActionReturnValueCannotBeNull(typeof(IActionResult)));
                    }
                }
                else if (executor.IsTypeAssignableFromIActionResult)
                {
                    if (_executor.IsMethodAsync)
                    {
                        result = (IActionResult)await _executor.ExecuteAsync(controller, orderedArguments);
                    }
                    else
                    {
                        result = (IActionResult)_executor.Execute(controller, orderedArguments);
                    }

                    if (result == null)
                    {
                        throw new InvalidOperationException(
                            Resources.FormatActionResult_ActionReturnValueCannotBeNull(_executor.TaskGenericType ?? returnType));
                    }
                }
                else if (!executor.IsMethodAsync)
                {
                    var resultAsObject = executor.Execute(controller, orderedArguments);
                    result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject)
                    {
                        DeclaredType = returnType,
                    };
                }
                else if (executor.TaskGenericType != null)
                {
                    var resultAsObject = await executor.ExecuteAsync(controller, orderedArguments);
                    result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject)
                    {
                        DeclaredType = executor.TaskGenericType,
                    };
                }
                else
                {
                    // This will be the case for types which have derived from Task and Task or non Task types.
                    throw new InvalidOperationException(Resources.FormatActionExecutor_UnexpectedTaskInstance(
                        executor.MethodInfo.Name,
                        executor.MethodInfo.DeclaringType));
                }

                _result = result;
             
            

        }

いずれの戻り値タイプもexecutorを呼び出す.Executeはコントローラメソッドの呼び出しを完了し、結果を取得します.executerは上記のObjectMethodExecuterです.Executeメソッドの実装だけを見てみましょう.
public object Execute(object target, object[] parameters)
        {
            return _executor(target, parameters);
        }

この中にまた一つ_executor、delegate object ActionExecutor(object target,object[]parameters)委任タイプです.ObjectMethodInvokerをインスタンス化するときにGetExecutorメソッドで作成されます.動的lambda式を使用します.この委任オブジェクトがあれば、コントローラメソッドを実行できます.
メソッドの実行後、メソッドは結果を返し、StateまでResult関連ステータスフローに進む.ResultInside状態の場合、InvokeResultAsyncメソッドを呼び出して動作結果を実行します.具体的なコードは以下の通りです.
protected async Task InvokeResultAsync(IActionResult result)
        {
            var actionContext = _actionContext;

            _diagnosticSource.BeforeActionResult(actionContext, result);

            try
            {
                await result.ExecuteResultAsync(actionContext);
            }
            finally
            {
                _diagnosticSource.AfterActionResult(actionContext, result);
            }
        }

ここまで1つのコントローラ動作が実行されても,本文は主体フローのみを紹介しており,他にも多くの実行状態については言及されていないことを指摘する必要がある.