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

.NET Core开发日志——Action的更多相关文章

  1. .NET Core开发日志——RequestDelegate

    本文主要是对.NET Core开发日志--Middleware的补遗,但是会从看起来平平无奇的RequestDelegate开始叙述,所以以其作为标题,也是合情合理. RequestDelegate是 ...

  2. .NET Core开发日志——Entity Framework与PostgreSQL

    Entity Framework在.NET Core中被命名为Entity Framework Core.虽然一般会用于对SQL Server数据库进行数据操作,但其实它还支持其它数据库,这里就以Po ...

  3. C#实现多级子目录Zip压缩解压实例 NET4.6下的UTC时间转换 [译]ASP.NET Core Web API 中使用Oracle数据库和Dapper看这篇就够了 asp.Net Core免费开源分布式异常日志收集框架Exceptionless安装配置以及简单使用图文教程 asp.net core异步进行新增操作并且需要判断某些字段是否重复的三种解决方案 .NET Core开发日志

    C#实现多级子目录Zip压缩解压实例 参考 https://blog.csdn.net/lki_suidongdong/article/details/20942977 重点: 实现多级子目录的压缩, ...

  4. .NET Core开发日志——从搭建开发环境开始

    .NET Core自2016年推出1.0版本开始,到目前已是2.1版本,在其roadmap计划里明年更会推出3.0版本,发展不可不谓之迅捷.不少公司在经过一个谨慎的观望期后,也逐步开始将系统升级至最新 ...

  5. .NET Core开发日志——Model Binding

    ASP.NET Core MVC中所提供的Model Binding功能简单但实用,其主要目的是将请求中包含的数据映射到action的方法参数中.这样就避免了开发者像在Web Forms时代那样需要从 ...

  6. .NET Core开发日志——简述路由

    有过ASP.NET或其它现代Web框架开发经历的开发者对路由这一名字应该不陌生.如果要用一句话解释什么是路由,可以这样形容:通过对URL的解析,指定相应的处理程序. 回忆下在Web Forms应用程序 ...

  7. .NET Core开发日志——结构化日志

    在.NET生态圈中,最早被广泛使用的日志库可能是派生自Java世界里的Apache log4net.而其后来者,莫过于NLog.Nlog与log4net相比,有一项较显著的优势,它支持结构化日志. 结 ...

  8. .NET Core开发日志——Edge.js

    最近在项目中遇到这样的需求:要将旧有系统的一部分业务逻辑集成到新的自动化流程工具中.这套正在开发的自动化工具使用的是C#语言,而旧有系统的业务逻辑则是使用AngularJS在前端构建而成.所以最初的考 ...

  9. .NET Core开发日志——Linux版本的SQL Server

    SQL Server 2017版本已经可以在Linux系统上安装,但我在尝试.NET Core跨平台开发的时候使用的是Mac系统,所以这里记录了在Mac上安装SQL Server的过程. 最新的SQL ...

随机推荐

  1. 安装 scws出现 autoconf 需要先安装

    安装在终端操作, curl -OL http://ftpmirror.gnu.org/autoconf/autoconf-latest.tar.gz tar xzf autoconf-latest.t ...

  2. js 引擎 和 html 渲染引擎

  3. JVM源码分析之Object.wait/notify实现(转载)

    最简单的东西,往往包含了最复杂的实现,因为需要为上层的存在提供一个稳定的基础,Object作为java中所有对象的基类,其存在的价值不言而喻,其中wait和notify方法的实现多线程协作提供了保证. ...

  4. SQLServer 数据库变成单个用户后无法访问问题的解决方法

    USE master; GO DECLARE @SQL VARCHAR(MAX); SET @SQL='' SELECT @SQL=@SQL+'; KILL '+RTRIM(SPID) FROM ma ...

  5. java证书

    默认情况下,密钥项存储在.keystore文件中,而可信的CA证书项存储在.cacerts文件中,该文件位于JRE安全目录中. 想在Linux环境下,用keytool命令检查一下一个证书,打keyto ...

  6. layui:根据行内某个值,设定该行得背景色

    done:function () { $("table tr").each(function () { var s = $(this).children().eq(1).text( ...

  7. mac中安装wxpython

    一.简介 wxPython是Python语言的一套优秀的GUI图形库,允许Python程序员很方便的创建完整的.功能键全的GUI用户界面. wxPython是作为优秀的跨平台GUI库wxWidgets ...

  8. Java多线程系列——过期的suspend()挂起、resume()继续执行线程

    简述 这两个操作就好比播放器的暂停和恢复. 但这两个 API 是过期的,也就是不建议使用的. 不推荐使用 suspend() 去挂起线程的原因,是因为 suspend() 在导致线程暂停的同时,并不会 ...

  9. 【Java】移动JDK路径后,修改环境变量不生效 Error: could not open `C:\Program Files\Java\jre1.8.0_131\lib\amd64\jvm.cfg'

    场景: JDK原先装在C盘的,现在移动到了D盘,并在环境变量修改了%JAVA_HOME%的新路径,但是CMD中输入java后依然报错. Error: could not open `C:\Progra ...

  10. PaaS 应用引擎

    这里主要是梳理一下应用引擎(XXXX App Engine),它一般被归类到PaaS领域.应用引擎即提供了各种编程语言开发的应用所需的一整套运行环境:它开箱即用,你只需部署应用的代码即可,无需前期的环 ...