一个http请求过来后,首先经过路由规则的匹配,找到最符合条件的的IRouter,然后调用IRouter.RouteAsync来设置RouteContext.Handler,最后把请求交给RouteContext.Handler来处理。在MVC中提供了两个IRouter实现,分别如下:

1,MvcAttributeRouteHandler

2,MvcRouteHandler

我们再来看一下UseMvc的实现逻辑

public static IApplicationBuilder UseMvc(
this IApplicationBuilder app,
Action<IRouteBuilder> configureRoutes)
{
。。。。。。
//实例化路由构造器
var routes = new RouteBuilder(app)
{
//设置默认处理器,就是路由符合条件时使用MvcRouteHandler来处理请求
DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
};
//配置路由规则
configureRoutes(routes);
//这句很重要,上面配置的全局的路由规则,我们同样可以在控制器或者控制器方法上使用RouteAttribute配置路由规则,这些规则会优先采用
routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));
//routes.Build方法生成IRouter对象,一会我们在看具体细节,然后通过UseRouter注册一个RouterMiddleware中间件
return app.UseRouter(routes.Build());
}

  

  上面的configureRoutes(routes)语句注册Route关联上了MvcRouteHandler,而routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices))注册AttributeRoute关联上了MvcAttributeRouteHandler,并且后者优先被匹配选择。

  在这两个RouterHanlder里做的一个最重要的工作就是进行动作选择,以MvcRouterHandler为例,代码如下:

     public Task RouteAsync(RouteContext context)
{
。。。。。。
//根据路由信息查找符合要求的ActionDescriptor集合
var candidates = _actionSelector.SelectCandidates(context);
if (candidates == null || candidates.Count == 0)
{
_logger.NoActionsMatched(context.RouteData.Values);
return TaskCache.CompletedTask;
}
//按照约束规则选择最符合要求的一个ActionDescriptor
var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates);
if (actionDescriptor == null)
{
_logger.NoActionsMatched(context.RouteData.Values);
return TaskCache.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();
}; return TaskCache.CompletedTask;
}

  在这里面借助一个ActionSelector来根据路由数据进行动作选择,得到符合要求的ActionDescriptor。ActionDescriptor是什么?它是一个动作的描述类,包含动作名称,约束条件等。它是如何得到的?

我们从IActionSelector.SelectCandidates开始进行跟踪,并根据MvcCoreServiceCollectionExtensions提供的AddMvcCoreServices的依赖注入配置,我们不难得到下面的调用关系:

在DefaultApplicationModelProvider.OnProvidersExecuting方法中通过反射方式解析程序集中的类信息。我们分析下控制器类分析的代码,这部分代码在CreateControllerModel方法中

protected virtual ControllerModel CreateControllerModel(TypeInfo typeInfo)
{
。。。。。。
//获取RouteAttribute特性信息,这里是采用循环的方式,直到找到第一个定义了IRouteTemplateProvider特性的类为止,所以如果子类没有配置RouteAttribute,就会采用父类的配置
IRouteTemplateProvider[] routeAttributes = null; do
{
routeAttributes = currentTypeInfo
.GetCustomAttributes(inherit: false)
.OfType<IRouteTemplateProvider>()
.ToArray(); if (routeAttributes.Length > 0)
{
// Found 1 or more route attributes.
break;
} currentTypeInfo = currentTypeInfo.BaseType.GetTypeInfo();
}
while (currentTypeInfo != objectTypeInfo); 。。。。。。 var controllerModel = new ControllerModel(typeInfo, attributes);
//创建Selectors,这个要在查找ActionDescriptor时使用
AddRange(controllerModel.Selectors, CreateSelectors(attributes));
//获取控制器名称
controllerModel.ControllerName =
typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) ?
typeInfo.Name.Substring(0, typeInfo.Name.Length - "Controller".Length) :
typeInfo.Name;
//把过滤器特性加入到Filters集合中
AddRange(controllerModel.Filters, attributes.OfType<IFilterMetadata>());
//获取路由规则数据,要求请求时某个路由数据必须等于设置的值,比如在控制器上设置[Area("test")],那只有当路由数据中包含了area且值等于test才用当前这个动作处理,当然大家可以自定义一些限制
foreach (var routeValueProvider in attributes.OfType<IRouteValueProvider>())
{
controllerModel.RouteValues.Add(routeValueProvider.RouteKey, routeValueProvider.RouteValue);
}
//api相关配置
var apiVisibility = attributes.OfType<IApiDescriptionVisibilityProvider>().FirstOrDefault();
if (apiVisibility != null)
{
controllerModel.ApiExplorer.IsVisible = !apiVisibility.IgnoreApi;
} var apiGroupName = attributes.OfType<IApiDescriptionGroupNameProvider>().FirstOrDefault();
if (apiGroupName != null)
{
controllerModel.ApiExplorer.GroupName = apiGroupName.GroupName;
} // 分析控制器是否实现了动作过滤器和结果过滤器接口,如果我们需要对一个控制器实现一个特殊的动作过滤器或结果过滤器,就不用再单独创建过滤器特性类了,直接让控制器实现接口即可,这个很方便
if (typeof(IAsyncActionFilter).GetTypeInfo().IsAssignableFrom(typeInfo) ||
typeof(IActionFilter).GetTypeInfo().IsAssignableFrom(typeInfo))
{
controllerModel.Filters.Add(new ControllerActionFilter());
}
if (typeof(IAsyncResultFilter).GetTypeInfo().IsAssignableFrom(typeInfo) ||
typeof(IResultFilter).GetTypeInfo().IsAssignableFrom(typeInfo))
{
controllerModel.Filters.Add(new ControllerResultFilter());
} return controllerModel;
}

  

  上面的方法结束后,就得到了一个ControllerModel对象,CreateActionModel方法是创建ActionModel对象,分析过程基本跟CreateControllerModel方法类似,就不再介绍,结果分析后,最后得到了下面的层次关系对象:

 ApplicationModel->ControllerModel->ActionModel

  得到最终的ApplicationModel后,再有ControllerActionDescriptorBuilder.Build(applicationModel)生成对应的ControllerActionDescriptor集合。

  再回到ActionSelector.SelectCandidates方法,在这个方法里面通过调用ActionSelectionDecisionTree.Select方法来选择符合要求的ActionDescriptor,实现代码如下:

 public IReadOnlyList<ActionDescriptor> Select(IDictionary<string, object> routeValues)
{
var results = new List<ActionDescriptor>();
Walk(results, routeValues, _root); return results;
}

  _root是一个DecisionTreeNode<ActionDescriptor>类型,它是通过DecisionTreeBuilder<ActionDescriptor>.GenerateTree生成的一个查找树。

DecisionTreeNode定义如下:

    internal class DecisionTreeNode<TItem>
{
//符合条件的ActionDescriptor集合
public IList<TItem> Matches { get; set; } // 分支规则
public IList<DecisionCriterion<TItem>> Criteria { get; set; }
}

  一个分支规则定义如下:

internal class DecisionCriterion<TItem>
{
public string Key { get; set; } public Dictionary<object, DecisionTreeNode<TItem>> Branches { get; set; }
}

查找逻辑:

1,判断当前结点上是否存在Matches,如果存在放到查找结果集里。

2,循环分支规则,获取分支规则key,按照key从路由数据中获取对应key的值,在通过这个值从Branches字典中查找对应DecisionTreeNode<TItem>

3,在分支Node上执行1,2步

4,返回最后的查找结果

  通过上面的步骤会得到所有符合要求的ActionDescriptor,然后调用SelectBestCandidate获取最符合条件的ActionDescriptor,如果最后查找到的ActionDescriptor不是一个,则报AmbiguousActionException异常。

回到MvcRouteHandler,在查找到ActionDescriptor之后,就设置context.Handler

context.Handler = (c) =>
{
var routeData = c.GetRouteData();
//根据actiondescriptor实例化ActionContext对象
var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
if (_actionContextAccessor != null)
{
_actionContextAccessor.ActionContext = actionContext;
}
//创建IActionInvoker
var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
if (invoker == null)
{
throw new InvalidOperationException(
Resources.FormatActionInvokerFactory_CouldNotCreateInvoker(
actionDescriptor.DisplayName));
}
//执行invoker处理请求
return invoker.InvokeAsync();
};

  在Hander中,根据查找到的ActionDescriptor实例化ActionContext,然后通过ActionInvokerFactory创建invoker,通过invoker执行ActionDescriptor对应的动作,并返回结果。

  先到这里,下面再继续介绍Invoker相关内容。

  

  

asp.net core mvc剖析:mvc动作选择的更多相关文章

  1. ASP.Net Core 2.2 MVC入门到基本使用系列 (一)

    本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...

  2. 在 Asp.Net Core 中安装 MVC

    在 ASP.NET Core 中安装 MVC 到目前为止,我们在本系列视频中使用的 ASP.NET Core 项目是使用“空”项目模板生成的.目前这个项目没有设置和安装 MVC. 两个步骤学会在 AS ...

  3. ASP.NET CORE 1.0 MVC API 文档用 SWASHBUCKLE SWAGGER实现

    from:https://damienbod.com/2015/12/13/asp-net-5-mvc-6-api-documentation-using-swagger/ 代码生成工具: https ...

  4. ASP.NET Core 2.0 MVC项目实战

    一.前言 毕业后入职现在的公司快有一个月了,公司主要的产品用的是C/S架构,再加上自己现在还在学习维护很老的delphi项目,还是有很多不情愿的.之前实习时主要是做.NET的B/S架构的项目,主要还是 ...

  5. ASP.Net Core 2.2 MVC入门到基本使用系列 (二)

    本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...

  6. ASP.Net Core 2.2 MVC入门到基本使用系列 (三)

    本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...

  7. ASP.Net Core 2.2 MVC入门到基本使用系列 (四)

    本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...

  8. asp.net core 3.0 MVC JSON 全局配置

    asp.net core 3.0 MVC JSON 全局配置 System.Text.Json(default) startup配置代码如下: using System.Text.Encodings. ...

  9. 基础教程:ASP.NET Core 2.0 MVC筛选器

    问题 如何在ASP.NET Core的MVC请求管道之前和之后运行代码. 解 在一个空的项目中,更新 Startup 类以添加MVC的服务和中间件. publicvoid ConfigureServi ...

  10. asp.net core 系列 6 MVC框架路由(下)

    一.URL 生成 接着上篇讲MVC的路由,MVC 应用程序可以使用路由的 URL 生成功能,生成指向操作的 URL 链接. 生成 URL 可消除硬编码 URL,使代码更稳定.更易维护. 此部分重点介绍 ...

随机推荐

  1. qlv格式转换成MP4格式

    腾讯视频下载:1.先下载腾讯的客户端--->播放视频在客户端 2.播放视频一段后停止 3.点击腾讯客户端的右上角的设置 4.复制缓存地址(注意:在此之前需要设置"显示隐藏的文件夹&qu ...

  2. bootstrap 基本页面元素,代码,列表

    bootstrap 基本页面元素,代码,列表 <!DOCTYPE html> <html> <head> <title></title> & ...

  3. HMX-Server C++ 分步式服务器大版本更新了(有源码)

    原文地址:http://www.cnblogs.com/hellohuang/p/6294763.html # HMX-ServerHMX-Server分步式服务器框架,主要分为网关.登录.世界.场景 ...

  4. 对lua中_ENV表的理解(lua5.2版本以后)

    当我拿到_ENV表的时候,会去想这个_ENV表是干什么用的? 首先看如下代码: print(_ENV) --0x1d005f0 print(_G) --0x1d005f0 ViewCode 看了上面的 ...

  5. opp(Object Oriented Programming)

    嗯,昨天忙了一天没来及发,过年啊,打扫啊,什么搽窗户啊,拖地啊,整理柜子啊,什么乱七八糟的都有,就是一个字,忙. 好了,废话也不多说,把自己学到的放上来吧.嗯,说什么好呢,就说原型链啊 原型对象 每个 ...

  6. Android几行代码实现实时监听微信聊天

    实现效果: 实时监听当前聊天页面的最新一条消息,如图:            实现原理: 同样是利用AccessibilityService辅助服务,关于这个服务类还不了解的同学可以先看下我上一篇关于 ...

  7. JAVA_file(1)

    1.基本概念的理解 绝对路径:绝对路径就是你的主页上的文件或目录在硬盘上真正的路径,(URL和物理路径)例如:C:xyz est.txt 代表了test.txt文件的绝对路径.http://www.s ...

  8. JAVA逻辑运算符

    逻辑运算符,用于链接boolean类型的表达式. AND与 (&)OR或 (|)XOR异或 (^)Not非 (!)AND双与短路 (&&)OR双与短路 (||) 与(& ...

  9. Android微信朋友圈全文、收起功能

    在众多的社交类软件中,朋友圈是必不可少的,可以与好友.同学等分享自己的日常和有意思的事情,在开发社交类App时,朋友圈发表的内容你不可能让他全部显示,全部显示的话用户体验度会非常不好,这时就要用到全文 ...

  10. 一个普通底层.NET程序员关于职场瓶颈期的思考,辗转自我提升/跳槽/转行之间

    徒有工龄,没技术没学历没平台没家底,工作几年,无车无房无存款还前景不明. 时常有身边的亲友问怎么学开发怎么转互联网,说起IT行业都说工资高,动辄月薪上万动辄年薪几十万. 再看看自己,我可能是假的程序员 ...