探索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处理了。