• .NET Core开发日志——Action


    在叙述Controller一文中,有一处未做解释,即CreateControllerFactory方法中ControllerActionDescriptor参数是如何产生的。这是因为其与Action的关联性更大,所以放在本文中继续描述。

    回到MvcRouteHandler或者MvcAttributeRouteHandler的方法中:

    public Task RouteAsync(RouteContext context)
    {
        ...
    
        var candidates = _actionSelector.SelectCandidates(context);
        if (candidates == null || candidates.Count == 0)
        {
            _logger.NoActionsMatched(context.RouteData.Values);
            return Task.CompletedTask;
        }
    
        var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates);
        if (actionDescriptor == null)
        {
            _logger.NoActionsMatched(context.RouteData.Values);
            return Task.CompletedTask;
        }
    
        context.Handler = (c) =>
        {
            var routeData = c.GetRouteData();
    
            var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
            if (_actionContextAccessor != null)
            {
                _actionContextAccessor.ActionContext = actionContext;
            }
    
            var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
            if (invoker == null)
            {
                throw new InvalidOperationException(
                    Resources.FormatActionInvokerFactory_CouldNotCreateInvoker(
                        actionDescriptor.DisplayName));
            }
    
            return invoker.InvokeAsync();
        };
    
        ...
    }
    

    不难发现作为源头的ActionContext中传入了actionDescriptor,而这个参数的值是在ActionSelector中被筛选出来的。

    public IReadOnlyList<ActionDescriptor> SelectCandidates(RouteContext context)
    {
        ...
    
        var cache = Current;
    
        // The Cache works based on a string[] of the route values in a pre-calculated order. This code extracts
        // those values in the correct order.
        var keys = cache.RouteKeys;
        var values = new string[keys.Length];
        for (var i = 0; i < keys.Length; i++)
        {
            context.RouteData.Values.TryGetValue(keys[i], out object value);
    
            if (value != null)
            {
                values[i] = value as string ?? Convert.ToString(value);
            }
        }
        
        if (cache.OrdinalEntries.TryGetValue(values, out var matchingRouteValues) ||
            cache.OrdinalIgnoreCaseEntries.TryGetValue(values, out matchingRouteValues))
        {
            Debug.Assert(matchingRouteValues != null);
            return matchingRouteValues;
        }
    
        _logger.NoActionsMatched(context.RouteData.Values);
        return EmptyActions;
    }
    

    然后可供筛选的ActionDescriptors集合又是来自ActionDescriptorCollectionProvider类。

    private Cache Current
    {
        get
        {
            var actions = _actionDescriptorCollectionProvider.ActionDescriptors;
            var cache = Volatile.Read(ref _cache);
    
            if (cache != null && cache.Version == actions.Version)
            {
                return cache;
            }
    
            cache = new Cache(actions);
            Volatile.Write(ref _cache, cache);
            return cache;
        }
    }
    

    它的内部又再调用了ControllerActionDescriptorProvider类的OnProvidersExecuting方法。

    public ActionDescriptorCollection ActionDescriptors
    {
        get
        {
            if (_collection == null)
            {
                UpdateCollection();
            }
    
            return _collection;
        }
    }
    
    private void UpdateCollection()
    {
        var context = new ActionDescriptorProviderContext();
    
        for (var i = 0; i < _actionDescriptorProviders.Length; i++)
        {
            _actionDescriptorProviders[i].OnProvidersExecuting(context);
        }
    
        for (var i = _actionDescriptorProviders.Length - 1; i >= 0; i--)
        {
            _actionDescriptorProviders[i].OnProvidersExecuted(context);
        }
    
        _collection = new ActionDescriptorCollection(
            new ReadOnlyCollection<ActionDescriptor>(context.Results),
            Interlocked.Increment(ref _version));
    }
    

    调用链继续深入到DefaultApplicationModelProvider之中。

    public void OnProvidersExecuting(ActionDescriptorProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }
    
        foreach (var descriptor in GetDescriptors())
        {
            context.Results.Add(descriptor);
        }
    }
    
    protected internal IEnumerable<ControllerActionDescriptor> GetDescriptors()
    {
        var applicationModel = BuildModel();
        ApplicationModelConventions.ApplyConventions(applicationModel, _conventions);
        return ControllerActionDescriptorBuilder.Build(applicationModel);
    }
    
    protected internal ApplicationModel BuildModel()
    {
        var controllerTypes = GetControllerTypes();
        var context = new ApplicationModelProviderContext(controllerTypes);
    
        for (var i = 0; i < _applicationModelProviders.Length; i++)
        {
            _applicationModelProviders[i].OnProvidersExecuting(context);
        }
    
        for (var i = _applicationModelProviders.Length - 1; i >= 0; i--)
        {
            _applicationModelProviders[i].OnProvidersExecuted(context);
        }
    
        return context.Result;
    }
    
    private IEnumerable<TypeInfo> GetControllerTypes()
    {
        var feature = new ControllerFeature();
        _partManager.PopulateFeature(feature);
    
        return feature.Controllers;
    }
    

    到了这里终于可以看到Action的影子,虽然现在还只是ActionModel。

    public virtual void OnProvidersExecuting(ApplicationModelProviderContext context)
    {
        ...
    
        foreach (var controllerType in context.ControllerTypes)
        {
            var controllerModel = CreateControllerModel(controllerType);
            if (controllerModel == null)
            {
                continue;
            }
    
            context.Result.Controllers.Add(controllerModel);
            controllerModel.Application = context.Result;
    
            ...
    
            foreach (var methodInfo in controllerType.AsType().GetMethods())
            {
                var actionModel = CreateActionModel(controllerType, methodInfo);
                if (actionModel == null)
                {
                    continue;
                }
    
                actionModel.Controller = controllerModel;
                controllerModel.Actions.Add(actionModel);
    
                foreach (var parameterInfo in actionModel.ActionMethod.GetParameters())
                {
                    var parameterModel = CreateParameterModel(parameterInfo);
                    if (parameterModel != null)
                    {
                        parameterModel.Action = actionModel;
                        actionModel.Parameters.Add(parameterModel);
                    }
                }
            }
        }
    }
    

    利用ControllerActionDescriptorBuilder类的Build方法,可以得到预期的ControllerActionDescriptor。

    public static IList<ControllerActionDescriptor> Build(ApplicationModel application)
    {
        var actions = new List<ControllerActionDescriptor>();
    
        var methodInfoMap = new MethodToActionMap();
    
        var routeTemplateErrors = new List<string>();
        var attributeRoutingConfigurationErrors = new Dictionary<MethodInfo, string>();
    
        foreach (var controller in application.Controllers)
        {
            // Only add properties which are explicitly marked to bind.
            // The attribute check is required for ModelBinder attribute.
            var controllerPropertyDescriptors = controller.ControllerProperties
                .Where(p => p.BindingInfo != null)
                .Select(CreateParameterDescriptor)
                .ToList();
            foreach (var action in controller.Actions)
            {
                // Controllers with multiple [Route] attributes (or user defined implementation of
                // IRouteTemplateProvider) will generate one action descriptor per IRouteTemplateProvider
                // instance.
                // Actions with multiple [Http*] attributes or other (IRouteTemplateProvider implementations
                // have already been identified as different actions during action discovery.
                var actionDescriptors = CreateActionDescriptors(application, controller, action);
    
                foreach (var actionDescriptor in actionDescriptors)
                {
                    actionDescriptor.ControllerName = controller.ControllerName;
                    actionDescriptor.ControllerTypeInfo = controller.ControllerType;
    
                    AddApiExplorerInfo(actionDescriptor, application, controller, action);
                    AddRouteValues(actionDescriptor, controller, action);
                    AddProperties(actionDescriptor, action, controller, application);
    
                    actionDescriptor.BoundProperties = controllerPropertyDescriptors;
    
                    if (IsAttributeRoutedAction(actionDescriptor))
                    {
                        // Replaces tokens like [controller]/[action] in the route template with the actual values
                        // for this action.
                        ReplaceAttributeRouteTokens(actionDescriptor, routeTemplateErrors);
                    }
                }
    
                methodInfoMap.AddToMethodInfo(action, actionDescriptors);
                actions.AddRange(actionDescriptors);
            }
        }
    
        ...
    
        return actions;
    }
    

    ControllerActionDescriptor包含了足以构建Controller与Action的属性。

    public string ControllerName { get; set; }
    
    public virtual string ActionName { get; set; }
    
    public MethodInfo MethodInfo { get; set; }
    
    public TypeInfo ControllerTypeInfo { get; set; }
    
    public IList<ParameterDescriptor> Parameters { get; set; }
    

    Controller的构建已经介绍过了,现在该谈谈关于Action的。

    先找到创建ControllerActionInvokerCacheEntry对象的ControllerActionInvokerCache类的GetCachedResult方法。可以看到两个关键参数objectMethodExecutor与actionMethodExecutor的创建方式。

    public (ControllerActionInvokerCacheEntry cacheEntry, IFilterMetadata[] filters) GetCachedResult(ControllerContext controllerContext)
    {
        var cache = CurrentCache;
        var actionDescriptor = controllerContext.ActionDescriptor;
    
        IFilterMetadata[] filters;
        if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry))
        {
            ...
    
            var objectMethodExecutor = ObjectMethodExecutor.Create(
                actionDescriptor.MethodInfo,
                actionDescriptor.ControllerTypeInfo,
                parameterDefaultValues);
    
            ...
    
            var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
    
            cacheEntry = new ControllerActionInvokerCacheEntry(
                filterFactoryResult.CacheableFilters, 
                controllerFactory, 
                controllerReleaser,
                propertyBinderFactory,
                objectMethodExecutor,
                actionMethodExecutor);
            cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry);
        }
        ...
    
        return (cacheEntry, filters);
    }
    

    再到ControllerActionInvoker类的Next方法中跟踪到State.ActionInside环节:

    case State.ActionInside:
        {
            var task = InvokeActionMethodAsync();
            if (task.Status != TaskStatus.RanToCompletion)
            {
                next = State.ActionEnd;
                return task;
            }
    
            goto case State.ActionEnd;
        }
    

    终于可以找到创建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(objectMethodExecutor, controller, orderedArguments);
            if (actionResultValueTask.IsCompletedSuccessfully)
            {
                result = actionResultValueTask.Result;
            }
            else
            {
                result = await actionResultValueTask;
            }
    
            _result = result;
            logger.ActionMethodExecuted(controllerContext, result, stopwatch.GetElapsedTime());
        }
        ...
    }
    

    核心的代码是这一句actionMethodExecutor.Execute(objectMethodExecutor, controller, orderedArguments)

    actionMethodExecutor与objectMethodExecutor即是之前生成ControllerActionInvokerCacheEntry对象时传入的两个参数,controller是在State.ActionBegin环节通过_instance = _cacheEntry.ControllerFactory(controllerContext);生成的。orderedArguments是Action方法所需的参数。

    至于更详细的创建过程,可以到ActionMethodExecutor类与ObjectMethodExecutor类中探寻,主要是涉及反射相关的知识,这里就不做进一步解释了。

  • 相关阅读:
    阿里云SQL Server远程连接配置
    RSA签名验证无法通过,检查以下部分
    windows开机自动登录
    c# 进程调用exe
    JavaScript console控制台调试 post
    Tesseract-OCR 训练教程(二) 合并新的训练文件
    获取手机唯一标识
    sqlserver 日期与字符串之间的转换
    linq根据英文首字母姓名排序
    js调用浏览器下载
  • 原文地址:https://www.cnblogs.com/kenwoo/p/9499725.html
Copyright © 2020-2023  润新知