探索ASP.NET MVC框架之路由系统

引言

  对于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来查看其源码。源码如下(源码经过适当筛选):

 1 public class UrlRoutingModule : IHttpModule
 2     {
 3         private static readonly object _contextKey = new object();
 4         private RouteCollection _routeCollection;
 5         public RouteCollection RouteCollection
 6         {
 7             get
 8             {
 9                 if (this._routeCollection == null)
10                 {
11                     this._routeCollection = RouteTable.Routes;
12                 }
13                 return this._routeCollection;
14             }
15             [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
16             set
17             {
18                 this._routeCollection = value;
19             }
20         }
21         public UrlRoutingModule()
22         {
23         }
24         
25         protected virtual void Init(HttpApplication application)
26         {
27             if (application.Context.Items[UrlRoutingModule._contextKey] != null)
28             {
29                 return;
30             }
31             application.Context.Items[UrlRoutingModule._contextKey] = UrlRoutingModule._contextKey;
32             application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
33         }
34         
35         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         }
73         private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
74         {
75             HttpApplication httpApplication = (HttpApplication)sender;
76             HttpContextBase context = new HttpContextWrapper(httpApplication.Context);
77             this.PostResolveRequestCache(context);
78         }
79         
80         protected virtual void Dispose()
81         {
82         }
83     }

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

  分析PostResolveRequestCache方法

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

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

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

 1 public RouteData GetRouteData(HttpContextBase httpContext)
 2 {
 3     using (this.GetReadLock())
 4     {
 5         foreach (RouteBase current in this)
 6         {
 7             RouteData routeData = current.GetRouteData(httpContext);
 8             if (routeData != null)
 9             {
10                 RouteData result;
11                 if (!current.RouteExistingFiles)
12                 {
13                     if (!flag2)
14                     {
15                         flag = this.IsRouteToExistingFile(httpContext);
16                     }
17                     if (flag)
18                     {
19                         result = null;
20                         return result;
21                     }
22                 }
23                 result = routeData;
24                 return result;
25             }
26         }
27     }
28     return null;
29 }

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

 1 public RouteCollection RouteCollection
 2         {
 3             get
 4             {
 5                 if (this._routeCollection == null)
 6                 {
 7                     this._routeCollection = RouteTable.Routes;
 8                 }
 9                 return this._routeCollection;
10             }
11             [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
12             set
13             {
14                 this._routeCollection = value;
15             }
16         }

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

 1 public abstract class RouteBase
 2 {
 3     private bool _routeExistingFiles = true;
 4     public bool RouteExistingFiles
 5     {
 6         get
 7         {
 8             return this._routeExistingFiles;
 9         }
10         set
11         {
12             this._routeExistingFiles = value;
13         }
14     }
15     public abstract RouteData GetRouteData(HttpContextBase httpContext);
16     public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
17 }

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

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

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

 1 public override RouteData GetRouteData(HttpContextBase httpContext)
 2         {
 3             string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
 4             RouteValueDictionary routeValueDictionary = this._parsedRoute.Match(virtualPath, this.Defaults);
 5             if (routeValueDictionary == null)
 6             {
 7                 return null;
 8             }
 9             RouteData routeData = new RouteData(this, this.RouteHandler);
10             if (!this.ProcessConstraints(httpContext, routeValueDictionary, RouteDirection.IncomingRequest))
11             {
12                 return null;
13             }
14             foreach (KeyValuePair<string, object> current in routeValueDictionary)
15             {
16                 routeData.Values.Add(current.Key, current.Value);
17             }
18             if (this.DataTokens != null)
19             {
20                 foreach (KeyValuePair<string, object> current2 in this.DataTokens)
21                 {
22                     routeData.DataTokens[current2.Key] = current2.Value;
23                 }
24             }
25             return routeData;
26         }

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

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

 1 IRouteHandler routeHandler = routeData.RouteHandler;
 2 if (routeHandler == null)
 3 {
 4     throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0]));
 5 }
 6 if (routeHandler is StopRoutingHandler)
 7 {
 8     return;
 9 }
10 RequestContext requestContext = new RequestContext(context, routeData);
11 context.Request.RequestContext = requestContext;
12 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);

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

 1 public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
 2         {
 3             if (routes == null)
 4             {
 5                 throw new ArgumentNullException("routes");
 6             }
 7             if (url == null)
 8             {
 9                 throw new ArgumentNullException("url");
10             }
11 
12             Route route = new Route(url, new MvcRouteHandler())
13             {
14                 Defaults = CreateRouteValueDictionary(defaults),
15                 Constraints = CreateRouteValueDictionary(constraints),
16                 DataTokens = new RouteValueDictionary()
17             };
18 
19             if ((namespaces != null) && (namespaces.Length > 0))
20             {
21                 route.DataTokens["Namespaces"] = namespaces;
22             }
23 
24             routes.Add(name, route);
25 
26             return route;
27         }

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

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

 1 public class MvcRouteHandler : IRouteHandler
 2     {
 3         private IControllerFactory _controllerFactory;
 4 
 5         public MvcRouteHandler()
 6         {
 7         }
 8 
 9         public MvcRouteHandler(IControllerFactory controllerFactory)
10         {
11             _controllerFactory = controllerFactory;
12         }
13 
14         protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
15         {
16             requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
17             return new MvcHandler(requestContext);
18         }
19 
20     }

  通过源代码我们看到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框架之路由系统)