一个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. js原生设计模式——13桥接模式(相同业务逻辑抽象化处理的职责链模式)

    桥接模式之多元化类之间的实例化调用实例 <!DOCTYPE html><html lang="en"><head>    <meta ch ...

  2. jQuery 的 ready 函数是如何工作的?(源码分析)

    如果你使用过 jQuery , 就必然使用过 ready 函数,它用来注册当页面准备好之后可以执行的函数. 问题来啦,我们的页面什么时候准备好了呢? 1. onload 事件 最基本的处理方式就是页面 ...

  3. MySQL5.6自动化部署(二进制)

    ###### 二进制自动安装数据库脚本root密码MANAGER将脚本和安装包放在/root目录即可############### ######数据库目录/usr/local/mysql####### ...

  4. Canvas createLinearGradient API

    createLinearGradient addColorStop <!DOCTYPE html> <html lang="en"> <head> ...

  5. jQ小图标上下滑动特效

    嗯,又到了,夜静饥寒的时候,手指颤抖,回望去,屋内满是寂静,寥寥绰影,咳咳咳,想我程序员之路还没到终点...就...咳咳咳 好了日常神经结束,还要涂我的唇膏.还剩下,最后一章,js动画(四),明天放上 ...

  6. POJ2503(hash)

    Babelfish Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 41263   Accepted: 17561 Descr ...

  7. HDU2579

    Dating with girls(2) Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Oth ...

  8. C# 6 与 .NET Core 1.0 高级编程 - C# 6 改进

    个人原创译文,转载请注明出处.有不对的地方欢迎指出与交流. 英文原文:Professional C# 6 and .NET Core 1.0 - What's New in C# 6 C# 6 改进 ...

  9. codeforces div2.C

    C. New Year and Rating time limit per test 2 seconds memory limit per test 256 megabytes input stand ...

  10. 你不知道的document.write

    使用document.write向文档输出写内容; document.write用法:document.write("要输出的内容"); 其实document.write()有两种 ...