为了账号安全,请及时绑定邮箱和手机立即绑定

.NET Core开发日志——Action

标签:
C#

在叙述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类中探寻,主要是涉及反射相关的知识,这里就不做进一步解释了。

原文出处:https://www.cnblogs.com/kenwoo/p/9499725.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消