zoukankan      html  css  js  c++  java
  • .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类中探寻,主要是涉及反射相关的知识,这里就不做进一步解释了。

  • 相关阅读:
    Maven 集成Tomcat插件
    dubbo 序列化 问题 属性值 丢失 ArrayList 解决
    docker 中安装 FastDFS 总结
    docker 从容器中拷文件到宿主机器中
    db2 相关命令
    Webphere WAS 启动
    CKEDITOR 4.6.X 版本 插件 弹出对话框 Dialog中 表格 Table 自定义样式Style 问题
    SpringMVC JSONP JSON支持
    CKEDITOR 3.4.2中 按钮事件中 动态改变图标和title 获取按钮
    git回退到远程某个版本
  • 原文地址:https://www.cnblogs.com/kenwoo/p/9499725.html
Copyright © 2011-2022 走看看