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

回到MvcRouteHandler或者MvcAttributeRouteHandler的方法中:

  1. public Task RouteAsync(RouteContext context)
  2. {
  3. ...
  4. var candidates = _actionSelector.SelectCandidates(context);
  5. if (candidates == null || candidates.Count == 0)
  6. {
  7. _logger.NoActionsMatched(context.RouteData.Values);
  8. return Task.CompletedTask;
  9. }
  10. var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates);
  11. if (actionDescriptor == null)
  12. {
  13. _logger.NoActionsMatched(context.RouteData.Values);
  14. return Task.CompletedTask;
  15. }
  16. context.Handler = (c) =>
  17. {
  18. var routeData = c.GetRouteData();
  19. var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
  20. if (_actionContextAccessor != null)
  21. {
  22. _actionContextAccessor.ActionContext = actionContext;
  23. }
  24. var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
  25. if (invoker == null)
  26. {
  27. throw new InvalidOperationException(
  28. Resources.FormatActionInvokerFactory_CouldNotCreateInvoker(
  29. actionDescriptor.DisplayName));
  30. }
  31. return invoker.InvokeAsync();
  32. };
  33. ...
  34. }

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

  1. public IReadOnlyList<ActionDescriptor> SelectCandidates(RouteContext context)
  2. {
  3. ...
  4. var cache = Current;
  5. // The Cache works based on a string[] of the route values in a pre-calculated order. This code extracts
  6. // those values in the correct order.
  7. var keys = cache.RouteKeys;
  8. var values = new string[keys.Length];
  9. for (var i = 0; i < keys.Length; i++)
  10. {
  11. context.RouteData.Values.TryGetValue(keys[i], out object value);
  12. if (value != null)
  13. {
  14. values[i] = value as string ?? Convert.ToString(value);
  15. }
  16. }
  17. if (cache.OrdinalEntries.TryGetValue(values, out var matchingRouteValues) ||
  18. cache.OrdinalIgnoreCaseEntries.TryGetValue(values, out matchingRouteValues))
  19. {
  20. Debug.Assert(matchingRouteValues != null);
  21. return matchingRouteValues;
  22. }
  23. _logger.NoActionsMatched(context.RouteData.Values);
  24. return EmptyActions;
  25. }

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

  1. private Cache Current
  2. {
  3. get
  4. {
  5. var actions = _actionDescriptorCollectionProvider.ActionDescriptors;
  6. var cache = Volatile.Read(ref _cache);
  7. if (cache != null && cache.Version == actions.Version)
  8. {
  9. return cache;
  10. }
  11. cache = new Cache(actions);
  12. Volatile.Write(ref _cache, cache);
  13. return cache;
  14. }
  15. }

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

  1. public ActionDescriptorCollection ActionDescriptors
  2. {
  3. get
  4. {
  5. if (_collection == null)
  6. {
  7. UpdateCollection();
  8. }
  9. return _collection;
  10. }
  11. }
  12. private void UpdateCollection()
  13. {
  14. var context = new ActionDescriptorProviderContext();
  15. for (var i = 0; i < _actionDescriptorProviders.Length; i++)
  16. {
  17. _actionDescriptorProviders[i].OnProvidersExecuting(context);
  18. }
  19. for (var i = _actionDescriptorProviders.Length - 1; i >= 0; i--)
  20. {
  21. _actionDescriptorProviders[i].OnProvidersExecuted(context);
  22. }
  23. _collection = new ActionDescriptorCollection(
  24. new ReadOnlyCollection<ActionDescriptor>(context.Results),
  25. Interlocked.Increment(ref _version));
  26. }

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

  1. public void OnProvidersExecuting(ActionDescriptorProviderContext context)
  2. {
  3. if (context == null)
  4. {
  5. throw new ArgumentNullException(nameof(context));
  6. }
  7. foreach (var descriptor in GetDescriptors())
  8. {
  9. context.Results.Add(descriptor);
  10. }
  11. }
  12. protected internal IEnumerable<ControllerActionDescriptor> GetDescriptors()
  13. {
  14. var applicationModel = BuildModel();
  15. ApplicationModelConventions.ApplyConventions(applicationModel, _conventions);
  16. return ControllerActionDescriptorBuilder.Build(applicationModel);
  17. }
  18. protected internal ApplicationModel BuildModel()
  19. {
  20. var controllerTypes = GetControllerTypes();
  21. var context = new ApplicationModelProviderContext(controllerTypes);
  22. for (var i = 0; i < _applicationModelProviders.Length; i++)
  23. {
  24. _applicationModelProviders[i].OnProvidersExecuting(context);
  25. }
  26. for (var i = _applicationModelProviders.Length - 1; i >= 0; i--)
  27. {
  28. _applicationModelProviders[i].OnProvidersExecuted(context);
  29. }
  30. return context.Result;
  31. }
  32. private IEnumerable<TypeInfo> GetControllerTypes()
  33. {
  34. var feature = new ControllerFeature();
  35. _partManager.PopulateFeature(feature);
  36. return feature.Controllers;
  37. }

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

  1. public virtual void OnProvidersExecuting(ApplicationModelProviderContext context)
  2. {
  3. ...
  4. foreach (var controllerType in context.ControllerTypes)
  5. {
  6. var controllerModel = CreateControllerModel(controllerType);
  7. if (controllerModel == null)
  8. {
  9. continue;
  10. }
  11. context.Result.Controllers.Add(controllerModel);
  12. controllerModel.Application = context.Result;
  13. ...
  14. foreach (var methodInfo in controllerType.AsType().GetMethods())
  15. {
  16. var actionModel = CreateActionModel(controllerType, methodInfo);
  17. if (actionModel == null)
  18. {
  19. continue;
  20. }
  21. actionModel.Controller = controllerModel;
  22. controllerModel.Actions.Add(actionModel);
  23. foreach (var parameterInfo in actionModel.ActionMethod.GetParameters())
  24. {
  25. var parameterModel = CreateParameterModel(parameterInfo);
  26. if (parameterModel != null)
  27. {
  28. parameterModel.Action = actionModel;
  29. actionModel.Parameters.Add(parameterModel);
  30. }
  31. }
  32. }
  33. }
  34. }

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

  1. public static IList<ControllerActionDescriptor> Build(ApplicationModel application)
  2. {
  3. var actions = new List<ControllerActionDescriptor>();
  4. var methodInfoMap = new MethodToActionMap();
  5. var routeTemplateErrors = new List<string>();
  6. var attributeRoutingConfigurationErrors = new Dictionary<MethodInfo, string>();
  7. foreach (var controller in application.Controllers)
  8. {
  9. // Only add properties which are explicitly marked to bind.
  10. // The attribute check is required for ModelBinder attribute.
  11. var controllerPropertyDescriptors = controller.ControllerProperties
  12. .Where(p => p.BindingInfo != null)
  13. .Select(CreateParameterDescriptor)
  14. .ToList();
  15. foreach (var action in controller.Actions)
  16. {
  17. // Controllers with multiple [Route] attributes (or user defined implementation of
  18. // IRouteTemplateProvider) will generate one action descriptor per IRouteTemplateProvider
  19. // instance.
  20. // Actions with multiple [Http*] attributes or other (IRouteTemplateProvider implementations
  21. // have already been identified as different actions during action discovery.
  22. var actionDescriptors = CreateActionDescriptors(application, controller, action);
  23. foreach (var actionDescriptor in actionDescriptors)
  24. {
  25. actionDescriptor.ControllerName = controller.ControllerName;
  26. actionDescriptor.ControllerTypeInfo = controller.ControllerType;
  27. AddApiExplorerInfo(actionDescriptor, application, controller, action);
  28. AddRouteValues(actionDescriptor, controller, action);
  29. AddProperties(actionDescriptor, action, controller, application);
  30. actionDescriptor.BoundProperties = controllerPropertyDescriptors;
  31. if (IsAttributeRoutedAction(actionDescriptor))
  32. {
  33. // Replaces tokens like [controller]/[action] in the route template with the actual values
  34. // for this action.
  35. ReplaceAttributeRouteTokens(actionDescriptor, routeTemplateErrors);
  36. }
  37. }
  38. methodInfoMap.AddToMethodInfo(action, actionDescriptors);
  39. actions.AddRange(actionDescriptors);
  40. }
  41. }
  42. ...
  43. return actions;
  44. }

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

  1. public string ControllerName { get; set; }
  2. public virtual string ActionName { get; set; }
  3. public MethodInfo MethodInfo { get; set; }
  4. public TypeInfo ControllerTypeInfo { get; set; }
  5. public IList<ParameterDescriptor> Parameters { get; set; }

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

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

  1. public (ControllerActionInvokerCacheEntry cacheEntry, IFilterMetadata[] filters) GetCachedResult(ControllerContext controllerContext)
  2. {
  3. var cache = CurrentCache;
  4. var actionDescriptor = controllerContext.ActionDescriptor;
  5. IFilterMetadata[] filters;
  6. if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry))
  7. {
  8. ...
  9. var objectMethodExecutor = ObjectMethodExecutor.Create(
  10. actionDescriptor.MethodInfo,
  11. actionDescriptor.ControllerTypeInfo,
  12. parameterDefaultValues);
  13. ...
  14. var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor);
  15. cacheEntry = new ControllerActionInvokerCacheEntry(
  16. filterFactoryResult.CacheableFilters,
  17. controllerFactory,
  18. controllerReleaser,
  19. propertyBinderFactory,
  20. objectMethodExecutor,
  21. actionMethodExecutor);
  22. cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry);
  23. }
  24. ...
  25. return (cacheEntry, filters);
  26. }

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

  1. case State.ActionInside:
  2. {
  3. var task = InvokeActionMethodAsync();
  4. if (task.Status != TaskStatus.RanToCompletion)
  5. {
  6. next = State.ActionEnd;
  7. return task;
  8. }
  9. goto case State.ActionEnd;
  10. }

终于可以找到创建Action的方法。

  1. private async Task InvokeActionMethodAsync()
  2. {
  3. var controllerContext = _controllerContext;
  4. var objectMethodExecutor = _cacheEntry.ObjectMethodExecutor;
  5. var controller = _instance;
  6. var arguments = _arguments;
  7. var actionMethodExecutor = _cacheEntry.ActionMethodExecutor;
  8. var orderedArguments = PrepareArguments(arguments, objectMethodExecutor);
  9. var diagnosticSource = _diagnosticSource;
  10. var logger = _logger;
  11. IActionResult result = null;
  12. try
  13. {
  14. diagnosticSource.BeforeActionMethod(
  15. controllerContext,
  16. arguments,
  17. controller);
  18. logger.ActionMethodExecuting(controllerContext, orderedArguments);
  19. var stopwatch = ValueStopwatch.StartNew();
  20. var actionResultValueTask = actionMethodExecutor.Execute(objectMethodExecutor, controller, orderedArguments);
  21. if (actionResultValueTask.IsCompletedSuccessfully)
  22. {
  23. result = actionResultValueTask.Result;
  24. }
  25. else
  26. {
  27. result = await actionResultValueTask;
  28. }
  29. _result = result;
  30. logger.ActionMethodExecuted(controllerContext, result, stopwatch.GetElapsedTime());
  31. }
  32. ...
  33. }

核心的代码是这一句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. 更新ruby:Error running 'requirements_osx_brew_update_system ruby-2.4.1报错解决

    更新ruby时,报错: Failed to update Homebrew, follow instructions here: https://github.com/Homebrew/homebre ...

  2. 使用vw做移动端页面的适配

    Flexible到今天也有几年的历史了,解救了很多同学针对于H5页面布局的适配问题.而这套方案也相对而言是一个较为成熟的方案.简单的回忆一下,当初为了能让页面更好的适配各种不同的终端,通过Hack手段 ...

  3. HDOJ 1393 Weird Clock(明确题意就简单了)

    Problem Description A weird clock marked from 0 to 59 has only a minute hand. It won't move until a ...

  4. hbase源码系列(十五)终结篇&Scan续集-->如何查询出来下一个KeyValue

    这是这个系列的最后一篇了,实在没精力写了,本来还想写一下hbck的,这个东西很常用,当hbase的Meta表出现错误的时候,它能够帮助我们进行修复,无奈看到3000多行的代码时,退却了,原谅我这点自私 ...

  5. android 自定义无限循环播放的viewPager。轮播ViewPager。实现循环播放 广告,主题内容,活动,新闻内容时。

    前言 实际项目需要一个 播放广告的控件,可能有多个广告图片.每个一段时间更换该图片.简单来说,就是一个 “循环播放图片”的控件. 间隔时间更换图片 一般来说,图片切换时需要有动画效果 需要支持手势,用 ...

  6. 【转】 Windows下配置Git

    [转自]http://blog.csdn.net/exlsunshine/article/details/18939329 1.从git官网下载windows版本的git:http://git-scm ...

  7. 网络协议TCP/IP、IPX/SPX、NETBEUI简介

    网络中不同的工作站,服务器之间能传输数据,源于协议的存在.随着网络的发展,不同的开发商开发了不同的通信方式.为了使通信成功可靠,网络中的所有主机都必须使用同一语言,不能带有方言.因而必须开发严格的标准 ...

  8. HBuilder搭配逍遥Android模拟器进行开发

    1.逍遥模拟器安装 地址: 点我下载 2.连接注意事项 a. 复制adb等文件 HBuilder安装目录中tools文件夹下的三个文件adb.exe,AdbWinApi.dll,AdbWinUsbAp ...

  9. Java面向对象的基本概念(对象、封装、继承、多态、抽象、接口、泛型)

    对象:是一个自包含的实体,用一组可识别的特征和行为来标识. 类:具有相同的属性和功能的对象的抽象合集.(类关键字class,首字母大写). 实例:就是一个真实的对象. 实例化:创建对象的过程,关键字是 ...

  10. phpstrom2018

    http://www.oyksoft.com/soft/40722.html?pc=1