MVC特性路由的提供机制
回顾:传统路由是如何提供的?
我们知道最终匹配的路由数据是保存在RouteData中的,而RouteData通常又是封装在RequestContext中的,他们是在哪里被创建的呢?没错,回到了UrlRoutingModule,我们知道UrlRoutingModule通过注册HttpApplication的PostResolveRequestCache方法来分发IHttpHandler决定ASP.NET请求最终交给哪个IHttpHandler去处理的。其实在这之前,首先会通过当前请求的HttpContextBase解析虚拟路径,匹配路由。然而这个工作是RouteCollection完成的。
public virtual void PostResolveRequestCache(HttpContextBase context)
{
RouteData routeData = this.RouteCollection.GetRouteData(context);
if (routeData == null)
{
return;
}
//省略了注册IHttpHandler的代码
}
反编译查看Route的GetRouteData方法
public RouteData GetRouteData(HttpContextBase httpContext)
{
if (httpContext == null)
{
throw new ArgumentNullException("httpContext");
}
if (httpContext.Request == null)
{
throw new ArgumentException(SR.GetString("RouteTable_ContextMissingRequest"), "httpContext");
}
if (base.Count == )
{
return null;
}
bool flag = false;
bool flag2 = false;
if (!this.RouteExistingFiles)
{
flag = this.IsRouteToExistingFile(httpContext);
flag2 = true;
if (flag)
{
return null;
}
}
using (this.GetReadLock())
{
foreach (RouteBase current in this)
{
RouteData routeData = current.GetRouteData(httpContext);
if (routeData != null)
{
RouteData result;
if (!current.RouteExistingFiles)
{
if (!flag2)
{
flag = this.IsRouteToExistingFile(httpContext);
}
if (flag)
{
result = null;
return result;
}
}
result = routeData;
return result;
}
}
}
return null;
}
这个方法乍一看也太复杂了吧,再看一下还是复杂,花了点时间理清了,就是这样子的

一句话解释:是文件都(RouteCollection和RouteBase)路由文件才成功匹配,不是文件只要路由规则匹配就成功匹配
正题:特性路由的初始化
特性路由是如何添加到路由系统中的呢?主要由AttributeRoutingMapper这个静态类实现的,先来跟随着源码一一道来。
在程序启动的时候,MVC会通过DefaultControllerFactory找到所有的控制器类型,为每一个控制器内注册的特性路由创建一个或多个Route。
public static void MapAttributeRoutes(RouteCollection routes, IInlineConstraintResolver constraintResolver)
{
DefaultControllerFactory typesLocator =
DependencyResolver.Current.GetService<IControllerFactory>() as DefaultControllerFactory
?? ControllerBuilder.Current.GetControllerFactory() as DefaultControllerFactory
?? new DefaultControllerFactory();
//获得所有的控制器类型
IReadOnlyList<Type> controllerTypes = typesLocator.GetControllerTypes();
//开始注册
MapAttributeRoutes(routes, controllerTypes, constraintResolver);
}
寻找特新路由的过程中会把所有创建的Route都放在SubRouteCollection这个RouteEntry只读集合中(RouteEntry封装一个Route和一个string类型的Name),最后MVC会将SubRouteCollection封装成一个Route对象(实际上是RouteBase的子类RouteCollectionRoute),添加到RouteCollection中。
public static void MapAttributeRoutes(RouteCollection routes, IEnumerable<Type> controllerTypes, IInlineConstraintResolver constraintResolver)
{
SubRouteCollection subRoutes = new SubRouteCollection();
AddRouteEntries(subRoutes, controllerTypes, constraintResolver);
IReadOnlyCollection<RouteEntry> entries = subRoutes.Entries;
if (entries.Count > )
{
RouteCollectionRoute aggregrateRoute = new RouteCollectionRoute(subRoutes);
routes.Add(aggregrateRoute);
}
}
那她是如何根据控制器来创建Route的呢?先要认识几个类型,ControllerDescriptor(控制器描述器),可以根据它获取应用在控制器上的各种特性,类似的还有ActionDescriptor(方法描述器,注意区分MethodInfo),还有ActionMethodSelector(方法选择器),方法选择器有两个属性,分别是DirectRouteMethods表示应用了特性路由的方法,StandardRouteMethods表示普通的方法。
具体处理每一个控制器类型的时候。会获取该控制器的方法选择器,对它的DirectRouteMethods和StandardRouteMethods分别处理。但两者有一个共同第一步,就是获取控制器前缀和Area前缀。控制器前缀直接从RoutePrefix特性中获取,Area前缀的话,会从RouteAreaAttribute获取,如果没有应用RouteAreaAttribute,那么就是控制器类型所在命名空间的最后一段。
internal static void AddRouteEntries(SubRouteCollection collector, ReflectedAsyncControllerDescriptor controller, IInlineConstraintResolver constraintResolver)
{
string prefix = GetRoutePrefix(controller);
RouteAreaAttribute area = controller.GetAreaFrom();
string areaName = controller.GetAreaName(area);
string areaPrefix = area != null ? area.AreaPrefix ?? area.AreaName : null;
AsyncActionMethodSelector actionSelector = controller.Selector;
//处理应用了特性路由的Action
foreach (var method in actionSelector.DirectRouteMethods)
{
//...
} //处理没有应用特性路由的Action
foreach (var method in actionSelector.StandardRouteMethods)
{
//...
}
}
处理DirectRouteMethods
首先会根据该Action的方法描述器获取应用的所有特性路由,针对每一个特性路由创建一个RouteEntry,这个核心任务是由DirectRouteBuilder的Builder方法实现的。DirectRouteBuilder包含了所有和Route相关的属性,同时还包含一个ActionDescriptor数组,大多数属性我们都是熟悉的,有必要介绍一下_actions字段,这个字段保存的是这条特性路由是应用在哪些方法上的(对于应用了特性路由的Action来说只有一个),Precedence是模板根据约束生成的一个字段,用来排序。
internal class DirectRouteBuilder : IDirectRouteBuilder
{
private readonly ActionDescriptor[] _actions;
private readonly bool _targetIsAction;
private string _template;
public DirectRouteBuilder(IReadOnlyCollection<ActionDescriptor> actions, bool targetIsAction)
{
if (actions == null)
{
throw new ArgumentNullException("actions");
}
_actions = actions.ToArray();
_targetIsAction = targetIsAction;
}
public string Name { get; set; } public string Template
{
get
{
return _template;
}
set
{
ParsedRoute = null;
_template = value;
}
}
public RouteValueDictionary Defaults { get; set; }
public RouteValueDictionary Constraints { get; set; }
public RouteValueDictionary DataTokens { get; set; }
public int Order { get; set; }
public decimal Precedence { get; set; }
public IReadOnlyCollection<ActionDescriptor> Actions
{
get { return _actions; }
} public bool TargetIsAction
{
get { return _targetIsAction; }
} public virtual RouteEntry Build()
{
//省略
}
}
对于应用了特性路由的Action来说。会找到所有应用在该Action上的特性路由,针对每一个特新路由创建相应的RouteEntry,而这一过程就是有RouteAttribute本身完成的。在这之前,会将特新路由的一些列属性封装到DirectRouteFactoryContext中。这里IDirectRouteFactory的唯一实现就是RouteAttribute。(不知道微软为什么不在这里直接写一个静态的Build方法,而要弄出DirectRouteBuilder的Builder和DirectRouteFactoryContext这两个类,个人觉得会简单很多)
internal static RouteEntry CreateRouteEntry(string areaPrefix, string prefix, IDirectRouteFactory factory, IReadOnlyCollection<ActionDescriptor> actions, IInlineConstraintResolver constraintResolver,
bool targetIsAction)
{
DirectRouteFactoryContext context = new DirectRouteFactoryContext(areaPrefix, prefix, actions, constraintResolver, targetIsAction);
RouteEntry entry = factory.CreateRoute(context);
return entry;
}
RouteAttribute的CreateRoute包含两本分,创建一个IDirectRouteBuilder,然后在调用他的Build方法。这里也表明了,我们可以通过RouteAttribute显示指定路由的优先级。
RouteEntry IDirectRouteFactory.CreateRoute(DirectRouteFactoryContext context)
{
IDirectRouteBuilder builder = context.CreateBuilder(Template);
builder.Name = Name;
builder.Order = Order;
return builder.Build();
}
CreateBuilder方法主要做了两件事,给路由添加控制器前缀,清除路由模板的内联约束。接下来看一下最核心的Build方法。
(1)将actions(类型为ActionDescriptor)存入DataTokens的MS_DirectRouteActions键
(2)如果定义路由的时候给定了Order,则将Order存入DataTokens的MS_DirectRouteOrder键
(3)如果特性路由的模板和约束条件改变了precedence值,也将它写入DataTokens的MS_DirectRoutePrecedence键
(4)如果actions对应的控制器描述器为同一个控制器类型,那就表示这条特性路由的控制器是有默认值的
(5)如果目标是应用了特新路由的Action,并且actions只有一个,name这条特性路由的Action也是有默认值的,同时将true写入DataTokens的MS_DirectRouteTargetIsAction键
(6)如果控制器使用的使用了RouteAreaAttribute特性,那么也将命名空间相关的值写入DataTokens中,UseNamespaceFallback用于以后控制器的查询。
public virtual RouteEntry Build()
{
if (ParsedRoute == null)
{
ParsedRoute = RouteParser.Parse(Template);
}
RouteValueDictionary defaults;
defaults = Copy(Defaults) ?? new RouteValueDictionary();
RouteValueDictionary constraints = Copy(Constraints);
RouteValueDictionary dataTokens = Copy(DataTokens) ?? new RouteValueDictionary();
dataTokens[RouteDataTokenKeys.Actions] = _actions;
ControllerDescriptor controllerDescriptor = GetControllerDescriptor();
RouteAreaAttribute area = controllerDescriptor.GetAreaFrom();
string areaName = controllerDescriptor.GetAreaName(area);
if (areaName != null)
{
dataTokens[RouteDataTokenKeys.Area] = areaName;
dataTokens[RouteDataTokenKeys.UseNamespaceFallback] = false;
Type controllerType = controllerDescriptor.ControllerType;
if (controllerType != null)
{
dataTokens[RouteDataTokenKeys.Namespaces] = new[] { controllerType.Namespace };
}
}
Route route = new Route(Template, defaults, constraints, dataTokens, routeHandler: null);
ConstraintValidation.Validate(route);
return new RouteEntry(Name, route);
}
处理StandardRouteMethods
对于没有应用特性路由的Action来说,和应用了特新路由的Action只有两点不一样,一个是RouteAttribute来说,他们的全都是Controller上的RouteAttribute提供的,还有就是方法描述器,是所有Action,为什么这样子呢?首先应用了特性路由的Action生成的Route会默认指定他的Action名称,由于现在的Action没有指定特性路由,全都是依靠Controller类型提供了,所以就不难理解了。
特性路由的提供机制
由于特性路由最终是以一个RouteCollectionRoute添加到RouteTable中的,所以他的重写了GetRouteData。这个类比较有意思,它本身是一个路由,但它却包括了一系列(特性)路由。
(1)在集合中找到所有和当前虚拟路径匹配的路由
(2)给RouteData的Values设置键MS_DirectRouteMatches,值为所有匹配的路由
(3)取出匹配的路由的第一个,如果有controller默认值,就给RouteData的Values设置一个controller的值
internal class RouteCollectionRoute : RouteBase, IReadOnlyCollection<RouteBase>
{
private readonly IReadOnlyCollection<RouteBase> _subRoutes;
public override RouteData GetRouteData(HttpContextBase httpContext)
{
List<RouteData> matches = new List<RouteData>();
foreach (RouteBase route in _subRoutes)
{
var match = route.GetRouteData(httpContext);
if (match != null)
{
matches.Add(match);
}
}
return CreateDirectRouteMatch(this, matches);
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
} public static RouteData CreateDirectRouteMatch(RouteBase route, List<RouteData> matches)
{
if (matches.Count == )
{
return null;
}
else
{
var routeData = new RouteData();
routeData.Route = route;
routeData.RouteHandler = new MvcRouteHandler();
ControllerDescriptor controllerDescriptor = matches[].GetTargetControllerDescriptor();
if (controllerDescriptor != null)
{
routeData.Values[RouteDataTokenKeys.Controller] = controllerDescriptor.ControllerName;
}
return routeData;
}
}
}
可能也许有人会有疑问,特性路由最后也没有匹配一个具体的Route,事实上就是这样子的,RouteCollectionRoute就是一个具体的路由,在控制器的匹配工作上还要用到这里的知识,所以任重而道远哈,不过说回来这篇博客是我有史以来耗时最长的一个了。。鼓励一下自己!!!
MVC特性路由的提供机制的更多相关文章
- asp.net mvc 特性路由(MapMvcAttributeRoutes)的应用
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/u012835032/article/details/51160824asp.net mvc 特性路由 ...
- ASP.NET Web API 2 中的特性路由
ASP.NET MVC 5.1 开始已经支持基于特性的路由(http://attributerouting.net),ASP.NET WEB API 2 同时也支持了这一特性. 启用特性路 由只需要在 ...
- Web API (四) 特性路由(Attribute Route)
特性路由 是Web API 2 中提出的一种新的类型的路由,正如其名称那样,它是通过特性(Attribute) 来定义路由的,相比之前的基于模式(Convertion Based)的路由,特性路由 能 ...
- MVC笔记--特性路由
物性路由:将路由和控制器放在一起,这样更简单方便,还可以处理复杂的路由场景 传统路由:集中.强制.基于代码风格来定义的. 每个MVC应用程序都需要路由来定义自己的处理请求方式,路由是MVC是应用程序的 ...
- C# MVC ( 添加路由规则以及路由的反射机制 )
在项目文件夹下找到 App_Start 下 找到 RouteConfig.cs文件 打开 (1) 约束的规则 从上往下 贪婪性 (2) 用 routes.MapRoute(...) 添加 ...
- ASP.NET没有魔法——ASP.NET MVC 直连路由(特性路由)
之前对Controller创建的分析中,知道了Controller的创建是有两个步骤组成,分别是Controller的类型查找以及根据类型创建Controller实例. 在查询Controller的类 ...
- ASP.NET Core MVC的路由参数中:exists后缀有什么作用,顺便谈谈路由匹配机制
我们在ASP.NET Core MVC中如果要启用Area功能,那么会看到在Startup类的Configure方法中是这么定义Area的路由的: app.UseMvc(routes => { ...
- [Web API] Web API 2 深入系列(5) 特性路由
目录 1. 特性路由注册 2. 路由解析 - 生成DataTokens - 选择HttpController - 选择Action 特性路由的目的在于更好的提供restful架构的接口,最近好忙(懒) ...
- MVC特性
MVC与ASP.NET MVC基础概念 MVC是Model-View-Controller的缩写. MVC将应用程序划分为3大组件:模型\视图\控制器. MVC不是ASP.NET所特有,它只是一种开发 ...
随机推荐
- 构建Docker平台【第二篇】安装 Docker
第一步:上传安装包和 docker 镜像 1.安装包: docker-engine-1.12.1-1.el7.centos.x86_64.rpm docker-engine-selinux-1.12. ...
- 每天一个Linux命令(11)--nl命令
nl命令在Linux系统中用来计算文件中行号.nl可以将输出的文件内容自动的加上行号,其默认的结果与cat -n 有点不太一样,nl可以将行号做比较多的显示设计,包括位数与是否自动不起0等等的功能. ...
- css中的那些布局
因为最近心血来潮,就总结了一下css中的几种常见的多列布局. 两列自适应布局 两列自适应布局算是css布局里面最基础的一种布局了,不少网站在使用. 这种布局通常是左侧固定,右边自适应,当然也有反过来的 ...
- Laravel事件Event
适用场景:记录文章浏览量 php artisan make:event 事件名 示例: php artisan make:event MyEvent Laravel目录\app\Events已经生成M ...
- Hibernate一对一外键映射
Hibernate 一对一外键映射 ------------------------------ ----- ...
- C#自动弹出窗口并定时自动关闭
最近做个小项目,用到一个小功能:后台线程定时查询数据库,不符合条件的记录弹出消息提醒(在窗口最前面),并且过几秒钟再自动关闭弹出的窗口. 所以从网上找来资料,如下: WinForm 下实现一个自动关闭 ...
- lua string.sub截取中英文
cocos2dx 2.x环境,要做一个截取很长的字符串的前100个字符显示的小功能. PC环境ok,出了ios包发现有时候这个字符串会显示不出,猜测了下可能是跟中文字在lua里每个字占3个字符有关,举 ...
- 《深入理解Java虚拟机》学习笔记之工具
善于利用工具,不仅可以加快我们分析数据,还可以快速定位和解决问题.现在我们就来看看虚拟机性能监控和故障处理工具. 在JDK的bin目录可以看到sun免费送给了我们很多小工具,这些工具虽然小巧但功能强大 ...
- KoaHub.js可借助 Babel 编译稳定运行在 Node.js 环境上
koahubjs KoaHub.js -- 基于 Koa.js 平台的 Node.js web 快速开发框架.可以直接在项目里使用 ES6/7(Generator Function, Class, A ...
- MP3 信息读取
MP3 信息读取 运行环境:Window7 64bit,.NetFramework4.61,C# 7.0: 编者:乌龙哈里 2017-03-13 参考: MP3-wikipedia ID3v1 MPE ...