asp.net core mvc剖析:mvc动作选择
一个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动作选择的更多相关文章
- ASP.Net Core 2.2 MVC入门到基本使用系列 (一)
本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...
- 在 Asp.Net Core 中安装 MVC
在 ASP.NET Core 中安装 MVC 到目前为止,我们在本系列视频中使用的 ASP.NET Core 项目是使用“空”项目模板生成的.目前这个项目没有设置和安装 MVC. 两个步骤学会在 AS ...
- 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 ...
- ASP.NET Core 2.0 MVC项目实战
一.前言 毕业后入职现在的公司快有一个月了,公司主要的产品用的是C/S架构,再加上自己现在还在学习维护很老的delphi项目,还是有很多不情愿的.之前实习时主要是做.NET的B/S架构的项目,主要还是 ...
- ASP.Net Core 2.2 MVC入门到基本使用系列 (二)
本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...
- ASP.Net Core 2.2 MVC入门到基本使用系列 (三)
本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...
- ASP.Net Core 2.2 MVC入门到基本使用系列 (四)
本教程会对基本的.Net Core 进行一个大概的且不会太深入的讲解, 在您看完本系列之后, 能基本甚至熟练的使用.Net Core进行Web开发, 感受到.Net Core的魅力. 本教程知识点大体 ...
- asp.net core 3.0 MVC JSON 全局配置
asp.net core 3.0 MVC JSON 全局配置 System.Text.Json(default) startup配置代码如下: using System.Text.Encodings. ...
- 基础教程:ASP.NET Core 2.0 MVC筛选器
问题 如何在ASP.NET Core的MVC请求管道之前和之后运行代码. 解 在一个空的项目中,更新 Startup 类以添加MVC的服务和中间件. publicvoid ConfigureServi ...
- asp.net core 系列 6 MVC框架路由(下)
一.URL 生成 接着上篇讲MVC的路由,MVC 应用程序可以使用路由的 URL 生成功能,生成指向操作的 URL 链接. 生成 URL 可消除硬编码 URL,使代码更稳定.更易维护. 此部分重点介绍 ...
随机推荐
- swift版 关于微信支付的那点事
今天心情那真是想要强奸吉娃娃的冲动 说白了就是不想做和工作沾边的任何事 但是也不能闲着啊 时间那么贵 之前就想把微信支付做一下 主要就是怕自己忘记了 今天难得有时间 就来简单的记录一下 旨 ...
- javascript 简单工厂
function detail() { this.imgArr = []; this.codeArr = []; } detail.prototype.addimg = function(img) { ...
- spring mvc handler的三种方式
springmvc.xml 三种方式不能针对一个controller同时使用 <?xml version="1.0" encoding="UTF-8"?& ...
- 为应用程序的选项卡及ActionBar设置样式
示例文件 flex-mobile-dev-tips-tricks-pt2.zip 关于Flex移动开发的提示和技巧有一系列文章,这是其中的第二部分.第一部分集中讲解如何在视图切换及应用程序操作切换之 ...
- iOS tabbar点击动画效果实现
正常情况下,我们点击tabbar都只有一个变色效果,但有时候,如果我们想给它添加一个点击动画,该如何做呢? 先上几个效果图: 1.先放大,再缩小 2.Z轴旋转 3.Y轴位移 ...
- 深入了解GCD
首先提出一些问题: dispatch_async 函数如何实现,分发到主队列和全局队列有什么区别,一定会新建线程执行任务么? dispatch_sync 函数如何实现,为什么说 GCD 死锁是队列导致 ...
- iOS核心笔记—MapKit框架-基础
1.MapKit框架简介: ✨了解:MapKit框架使用须知:①.MapKit框架中所有的数据类型的前缀都是MK:②.需要导入#import <MapKit/MapKit.h>头文件:③. ...
- asp.net权限认证篇外:集成域账号登录
在之前的我们已经讲过asp.net权限认证:Windows认证,现在我们来讲讲域账号登录, 这不是同一件事哦,windows认证更多的是对资源访问的一种权限管控,而域账号登录更多的是针对用户登录的认证 ...
- 【LeetCode题解】数组Array
1. 数组 直观地看,数组(Array)为一个二元组<index, value>的集合--对于每一个index,都有一个value与之对应.C语言中,以"连续的存储单元" ...
- SQL SPLIT2
CREATE FUNCTION F_SQLSERVER_SPLIT( @Long_str varchar ( 8000 ), @split_str varchar ( 100 )) ...