引言

  对于ASP.NET MVC的路由系统相信大家肯定不陌生。今天我们就深入ASP.NET的框架内部来看一下路由系统到底是怎么通过我们给出的地址(例如:/Home/Index)解析出Controller和Action。今天的这一篇文章我们就深入框架内部,看看里面的流程。

UrlRouteModule介绍

  ASP.NET MVC本质上是通过IHttpModule和IHttpHandler两个组件对ASP.NET框架进行扩展来实现的。ASP.NET 请求处理过程是基于管道模型的,这个管道模型是由多个HttpModule和HttpHandler组成,ASP.NET 把http请求依次传递给管道中各个HttpModule,最终被HttpHandler处理,处理完成后,再次经过管道中的HTTP模块,把结果返回给客户端。我们可以在每个HttpModule中都可以干预请求的处理过程。

  ASP.NET MVC就是通过一个自定义IHttpModule将Http请求成功ASP.NET处理管道中接管到MVC框架的。微软自己实现了这个自定义的IHttpModule,这就是我们今天要介绍的UrlRouteModule。这个类是在System.Web.Routing.dll中的。我们通过ILSpy来查看其源码。源码如下(源码经过适当筛选):

 public class UrlRoutingModule : IHttpModule
{
private static readonly object _contextKey = new object();
private RouteCollection _routeCollection;
public RouteCollection RouteCollection
{
get
{
if (this._routeCollection == null)
{
this._routeCollection = RouteTable.Routes;
}
return this._routeCollection;
}
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
set
{
this._routeCollection = value;
}
}
public UrlRoutingModule()
{
} protected virtual void Init(HttpApplication application)
{
if (application.Context.Items[UrlRoutingModule._contextKey] != null)
{
return;
}
application.Context.Items[UrlRoutingModule._contextKey] = UrlRoutingModule._contextKey;
application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
} public virtual void PostResolveRequestCache(HttpContextBase context)
36 {
37 RouteData routeData = this.RouteCollection.GetRouteData(context);
38 if (routeData == null)
39 {
40 return;
41 }
42 IRouteHandler routeHandler = routeData.RouteHandler;
43 if (routeHandler == null)
44 {
45 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0]));
46 }
47 if (routeHandler is StopRoutingHandler)
48 {
49 return;
50 }
51 RequestContext requestContext = new RequestContext(context, routeData);
52 context.Request.RequestContext = requestContext;
53 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
54 if (httpHandler == null)
55 {
56 throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), new object[]
57 {
58 routeHandler.GetType()
59 }));
60 }
61 if (!(httpHandler is UrlAuthFailureHandler))
62 {
63 context.RemapHandler(httpHandler);
64 return;
65 }
66 if (FormsAuthenticationModule.FormsAuthRequired)
67 {
68 UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
69 return;
70 }
71 throw new HttpException(401, SR.GetString("Assess_Denied_Description3"));
72 }
private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
{
HttpApplication httpApplication = (HttpApplication)sender;
HttpContextBase context = new HttpContextWrapper(httpApplication.Context);
this.PostResolveRequestCache(context);
} protected virtual void Dispose()
{
}
}

  我们看到UrlRouteModule继承自IHttpModule接口。这个接口非常简单,只有Init方法和Dispose方法。我们知道ASP.NET处理管线在处理请求过程中,会有19个事件可以让程序员自定义扩展,以便请求在执行完某一个步骤后,可以进行相关操作。UrlRouteModule通过Init方法注册PostResolveRequestCache事件处理函数,让管线处理到PostResolveRequestCache这一步时,调用我们的回调函数OnApplicationPostResolveRequestCache。下面我们来好好分析这个回调函数(代码的73行开始)。

  分析PostResolveRequestCache方法

  我们看到源代码中的红色部分就是回调函数的主体。第一行代码如下:

 RouteData routeData = this.RouteCollection.GetRouteData(context);

  我们看到它根据当前请求的上下文,来获取RouteData对象。我们进入GetRouteData看看里面的逻辑是什么。请看源码:

 public RouteData GetRouteData(HttpContextBase httpContext)
{
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;
}

  我们看到该方法内部使用循环来依次调用GetRouteData方法(代码的第7行)。很显然这边的this指的就是我们在程序中配置的路由表了。还记得PostResolveRequestCache方法中的this.RouteCollection吗?回到第一个代码片段第11行,我们看到如下代码(注意红色部分):

 public RouteCollection RouteCollection
{
get
{
if (this._routeCollection == null)
{
this._routeCollection = RouteTable.Routes;
}
return this._routeCollection;
}
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
set
{
this._routeCollection = value;
}
}

  看到这,我们应该明白RouteCollection里面存储的是什么了吧!里面存储的就是配置的所有的路由。我们继续往下探索,从current.GetRouteData(httpContext)开始,我们再次进入current对象(类型是RouteBase)的GetRouteData对象。我们看到了RouteBase的源码:

 public abstract class RouteBase
{
private bool _routeExistingFiles = true;
public bool RouteExistingFiles
{
get
{
return this._routeExistingFiles;
}
set
{
this._routeExistingFiles = value;
}
}
public abstract RouteData GetRouteData(HttpContextBase httpContext);
public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
}

  RouteBase是一个抽象类,GetRouteData方法的实现是根据具体的继承类型的实现为基础的。那么我们不禁要问哪一个类型继承自RouteBase呢?我们想想看,既然RouteTable.Routes里面存储的都是路由对象,那么我们添加路由对象时,添加的应该就是继承自RouteBase类型的派生类。想必这个大家一定不陌生。我们添加路由的时候除了使用MapRoute方法也可以使用Add方法。如下所示:

 routes.MapRoute("StaticRoute", "Content/CustomerJS.js",
new { controller = "Home", action = "Index" },
new string[] { "MyFirstMvcProject.Controllers" }); routes.Add("first", new Route("{controller}/{action}", new MvcRouteHandler()));

  我们看到MVC框架内部就是使用Route来继承RouteBase的。那么很显然,current.GetRouteData调用的肯定是派生类的方法,我们去Route对象中看看这个方法的具体实现。源码如下:

 public override RouteData GetRouteData(HttpContextBase httpContext)
{
string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring() + httpContext.Request.PathInfo;
RouteValueDictionary routeValueDictionary = this._parsedRoute.Match(virtualPath, this.Defaults);
if (routeValueDictionary == null)
{
return null;
}
RouteData routeData = new RouteData(this, this.RouteHandler);
if (!this.ProcessConstraints(httpContext, routeValueDictionary, RouteDirection.IncomingRequest))
{
return null;
}
foreach (KeyValuePair<string, object> current in routeValueDictionary)
{
routeData.Values.Add(current.Key, current.Value);
}
if (this.DataTokens != null)
{
foreach (KeyValuePair<string, object> current2 in this.DataTokens)
{
routeData.DataTokens[current2.Key] = current2.Value;
}
}
return routeData;
}

  我们看到在这个方法中,创建了RouteData对象,并且对URL路径进行了解析(this._parsedRoute.Match(virtualPath, this.Defaults)这一句)。并且后续还验证了路由规则的正则表达式和命名空间。同时我们也看到RouteData很多属性值都是在这里添加的。RouteData的RouteHandler也是取自Route的RouteHandler属性。

  到此为止,我们终于看清楚RouteData对象是如何创建的,属性值是如何进行赋值的。我们还是回到UrlRouteModule。我们看下面的代码:

 IRouteHandler routeHandler = routeData.RouteHandler;
if (routeHandler == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[]));
}
if (routeHandler is StopRoutingHandler)
{
return;
}
RequestContext requestContext = new RequestContext(context, routeData);
context.Request.RequestContext = requestContext;
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);

  我们看到下一步的操作就是获取RouteData中保存的RouteHandler,通过RouteHandler来找到下一步处理请求的Handler。我们这次通过route.MapRoute方法来探索。下面我们一起来看下我们注册路由时经常使用的MapRoute的内部实现是怎么样的?请看源码:

 public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
{
if (routes == null)
{
throw new ArgumentNullException("routes");
}
if (url == null)
{
throw new ArgumentNullException("url");
} Route route = new Route(url, new MvcRouteHandler())
{
Defaults = CreateRouteValueDictionary(defaults),
Constraints = CreateRouteValueDictionary(constraints),
DataTokens = new RouteValueDictionary()
}; if ((namespaces != null) && (namespaces.Length > ))
{
route.DataTokens["Namespaces"] = namespaces;
} routes.Add(name, route); return route;
}

  我们看到我们调用的MapRoute创建了Route对象(原来RouteTable.Routes里面存储的都是Route类型的实例(Route继承自RouteBase对象)),当然IHttpHandler设置为MvcRouteHandler。并且把默认值default、约束、命名空间的值都存储为RouteValueDictionary类型。那么显然RouteData的RouteHandler就是MvcRouteHandler。

  我们知道RouteData的RouteHandler是IRouteHandler类型的,MvcRouteHandler是IRouteHandler的具体实现。我们来看下MvcRouteHandler的源代码:

 public class MvcRouteHandler : IRouteHandler
{
private IControllerFactory _controllerFactory; public MvcRouteHandler()
{
} public MvcRouteHandler(IControllerFactory controllerFactory)
{
_controllerFactory = controllerFactory;
} protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
{
requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
return new MvcHandler(requestContext);
} }

  通过源代码我们看到MvcRouteHandler实现了IRouteHandler接口的GetHttpHandler方法。我们在UrlRouteModule中通过RouteData的RouteHandler属性获取HttpHandler其实调用的就是MvcRouteHandler的GetHttpHandler方法。我们看到最终返回的是MvcHandler类型。到此为止,我们就知道最终返回的IHttpHandler类型就是MvcHandler。请求的后续操作就交给这个HttpHandler处理了。

  相关总结

  1、我们经常使用的MapRoute并不是RouteCollection自带的方法,而是在MVC源码里提供的一个扩展方法。扩展类名是:RouteCollectionExtensions。

  2、Route继承自RouteBase抽象类。在获取RouteData的方法中,遍历RouteTable.Routes集合,将当前的请求的URL和路由模板进行匹配,这一过程实质调用的是Route类型的GetRouteData方法。

  3、RouteData的相关属性和RouteHandler都是从Route对象获取的。

  4、路由系统最终返回的IHttpHandler类型是MvcHandler类型,请求的后续操作就交给这个HttpHandler处理了。

探索ASP.NET MVC框架之路由系统的更多相关文章

  1. 探索ASP.NET MVC框架之控制器的查找与激活机制

    引言 前面一篇博文我们介绍了MVC框架的路由机制,我们知道一个URL请求如何从ASP.NET处理管线到达了IHttpHandler实例(MvcHandler).今天我们从MvcHandler来进行下一 ...

  2. ASP.NET MVC , ASP.NET Web API 的路由系统与 ASP.NET 的路由系统是怎么衔接的?

      ASP.NET MVC 的路由实际上是建立在 ASP.NET 的路由系统之上的. MVC 路由注册通常是这样的: RouteTable 是一个全局路由表, 它的 Routes 静态属性是一个 Ro ...

  3. 学习“迷你ASP.NET MVC框架”后的小结

    看蒋老师MVC的书第二个大收获可以是算是看了这个迷你ASP.NET MVC框架了,虽然它远不如真正ASP.NET MVC(下文简称“MVC”)那么复杂庞大,但在迷你版中绕来绕去也够呛的.这部分我看了几 ...

  4. ASP.NET MVC 的URL路由介绍

    在这个教程中,向你介绍每个ASP.NET MVC一个重要的特点叫做URL路由.URL路由模块是负责映射从浏览器请求到特定的控制器动作. 在教程的第一部分,你将学习标准路由表如何映射到控制器的动作.在教 ...

  5. 为ASP.NET MVC应用添加自定义路由

    这里,我们将学习如何给asp.net mvc应用添加自定义路由.用自定义路由来修改默认路由表. 对一些简单的asp.net mvc应用,默认的路由表就已经足够了.但是,当你需要创建特殊的路由时,就需要 ...

  6. BrnShop开源网上商城第二讲:ASP.NET MVC框架

    在团队设计BrnShop的web项目之初,我们碰到了两个问题,第一个是数据的复用和传递,第二个是大mvc框架和小mvc框架的选择.下面我依次来说明下. 首先是数据的复用和传递:对于BrnShop的每一 ...

  7. 重建程序员能力(3)-asp.net MVC框架增加Controller

        MVC在微软中提供的框架目前只是发现是asp.net用.另 8年前,我做了个MVC的Windows APP框架如果有兴趣我日后会介绍给大家,欢迎大家关注.MVC的概念网站上有很多,大家去查阅一 ...

  8. 写自己的ASP.NET MVC框架(下)

    上篇博客[写自己的ASP.NET MVC框架(上)] 我给大家介绍我的MVC框架对于Ajax的支持与实现原理.今天的博客将介绍我的MVC框架对UI部分的支持. 注意:由于这篇博客是基于前篇博客的,因此 ...

  9. 【翻译】ASP.NET MVC 5属性路由(转)

    转载链接:http://www.cnblogs.com/thestartdream/p/4246533.html 原文链接:http://blogs.msdn.com/b/webdev/archive ...

随机推荐

  1. LVM 类型的 Storage Pool - 每天5分钟玩转 OpenStack(8)

    LVM 类型的 Storage Pool 不仅一个文件可以分配给客户机作为虚拟磁盘,宿主机上 VG 中的 LV 也可以作为虚拟磁盘分配给虚拟机使用. 不过,LV 由于没有磁盘的 MBR 引导记录,不能 ...

  2. Python安装

    Win10 安装Python 1.官网下载Python安装包,一直点击下一步进行安装 安装完Python后要设置环境变量,右键我的电脑-->属性-->高级系统设置-->环境变量--& ...

  3. js的encodeURIComponent与java的URLEncoder的区别

    js中的encodeURIComponent这个函数和java中的URLEncoder有少数不一样的.如下表格就是区别 ascii java js   + %20 ! %21 ! ' %27 ' ( ...

  4. linux basic commands

    1. man - an interface to the on-line reference manuals $man man 2. apt - advanced package tool SEE A ...

  5. Ubuntu 网络参数设置

    修改网络设置 sudo vim /etc/network/interfaces 修改计算机名 sudo vim /etc/hostname sudo vim /etc/hosts

  6. JAVA JVM虚拟机选项:Xms Xmx PermSize MaxPermSize 区别

    Xms : 是指设定程序启动时占用内存大小.一般该值设置大的会使程序启动快,但是可能会使本机暂时变慢. Xmx : 是指设定程序运行期间最大可占用的内存大小,如果程序运行需要占用更多的内存,超出这个 ...

  7. [转]django自定义表单提交

    原文网址:http://www.cnblogs.com/retop/p/4677148.html 注:本人使用的Django1.8.3版本进行测试 除了使用Django内置表单,有时往往我们需要自定义 ...

  8. LeetCode:Word Ladder I II

    其他LeetCode题目欢迎访问:LeetCode结题报告索引 LeetCode:Word Ladder Given two words (start and end), and a dictiona ...

  9. DNS简析

    IntroductionName Server架构分层管理机制分层查询机制Name Server之间的Master-Slave架构DDNS底层协议配置文件/etc/hosts/etc/resov.co ...

  10. nodejs+express+mysql 增删改查

    之前,一直使用的是nodejs+thinkjs来完成自己所需的项目需求,而对于nodejs中另外一中应用框架express却了解的少之又少,这两天就简单的了解了一下如何使用express来做一些数据库 ...