学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC是如何运行的(二)
路由
对于一个ASP.NET MVC应用来说,针对HTTP请求的处理实现在目标Controller类型的某个Action,每个HTTP请求不在像ASP.NET Web Forms应用一样是针对一个物理文件,而是针对某个Controller的某个Action方法。目标Controller和Action的名称由HTTP请求的URL来决定,当ASP.NET MVC接收到抵达的请求后,其首要任务就是通过当前HTTP请求解析得到目标Controller和Action的名称,这个过程是通过ASP.NET MVC的路由系统来实现的。我们通过如下几个对象构建了一个简易的路由系统。
1.RouteData
ASP.NET定义了一个全局的路由表,路由表中的每个Route对象包含一个路由模板。目标Controller和Action的名称可以通过路由变量以占位符的形式定义在模板中,也可以作为路由对象的默认值(无须出现在路由模板中)。对于每一个抵达的HTTP请求,路由系统会遍历路由表并找到一个具有与当前请求URL模式相匹配的Route对象,然后利用它解析出以Controller和Action名称为核心的路由数据。在我们自建的ASP.NET MVC框架中,通过路由解析得到的路由数据通过具有如下定义的RouteData类型表示。
public class RouteData { public IDictionary<string, object> Values { get; private set; } public IDictionary<string, object> DataTokens { get; private set; } public IRouteHandler RouteHandler { get; set; } public RouteBase Route { get; set; } public RouteData() { this.Values = new Dictionary<string, object>(); this.DataTokens = new Dictionary<string, object>(); this.DataTokens.Add("namespaces", new List<string>()); } public string Controller { get { object controllerName = string.Empty; this.Values.TryGetValue("controller", out controllerName); return controllerName.ToString(); } } public string ActionName { get { object actionName = string.Empty; this.Values.TryGetValue("action", out actionName); return actionName.ToString(); } } }
RouteData定义了两个字典类型的属性Values和DataTokens,他们代表具有不同来源的路由变量,前者由对请求URL实施路由解析获得。表示Controller和Action名称的属性直接从Values属性的字典中提取,对应的Key分别为“controller”和“action”。
ASP.NET MVC的本质是由自定义的HttpModule和自定义的HttpHandler两个组件来实现的。HttpModule从RouteData对象的RouteHandler属性获得。RouteData的RouteHandler属性类型为IRouteHandler接口,该接口具有一个唯一的GetHttpHandler方法返回真正真正处理HTTP请求的HttpHandler对象。该方法有一个类型为RequestContext的参数。RequestContext表示当前(HTTP)请求的上下文,其核心就是对当前HttpContext和RouteData的封装。
public interface IRouteHandler { IHttpHandler GetHttpHandler(RequestContext requestContext); } public class RequestContext { public virtual HttpContextBase HttpContext { get; set; } public virtual RouteData RouteData { get; set; } }
2.Route和RouteTable
承载路由变量的RouteData对象由路由表中与当前请求相匹配的Route对象生成,可以通过RouteData的Route属性获得这个Route对象,该属性的类型为RouteBase。如下面的代码片段所示,RouteBase是一个抽象类,他仅仅包含一个返回类型为RouteData的GetRouteData方法。
public abstract class RouteBase { public abstract RouteData GetRouteData(HttpContextBase httpContext); }
RouteBase的GetRouteData方法具有一个类型为HttpContextBase的参数,它代表针对当前请求的HTTP上下文。当该方法被执行的时候,它会判断自身定义的路由规则是否与当前请求相匹配,并在成功匹配的情况下实施路由解析,并将得到的路由变量封装成RouteData对象返回。如果路由规则与当前请求不匹配,则该方法直接返回Null。
我们定义了如下一个继承自RouteBase的Route类型来完成具体的路由工作。一个Route对象具有一个代表路由模板的字符串类型的Url属性。在实现的GetRouteData方法中,我们通过HttpContextBase获取当前请求的URL,如果它与路由模板的模式相匹配,则创建一个RouteData对象作为返回值。对于返回的RouteData对象,其Values属性表示的字典对对象包含直接通过解析出来的变量,而对于DataTokens字典和RouteHandler属性,则直接取自Route对象的同名属性。
public class Route : RouteBase { public IRouteHandler RouteHandler { get; set; } public string Url { get; set; } public IDictionary<string, object> DataTokens { get; set; } public Route() { this.DataTokens = new Dictionary<string, object>(); this.RouteHandler = new MvcRouteHandler(); } public override RouteData GetRouteData(HttpContextBase httpContext) { IDictionary<string, object> variables; if (this.Match(httpContext.Request .AppRelativeCurrentExecutionFilePath.Substring(2), out variables)) { RouteData routeData = new RouteData(); foreach (var item in variables) { routeData.Values.Add(item.Key, item.Value); } foreach (var item in DataTokens) { routeData.DataTokens.Add(item.Key, item.Value); } routeData.RouteHandler = this.RouteHandler; return routeData; } return null; } protected bool Match(string requestUrl,out IDictionary<string, object> variables) { variables = new Dictionary<string, object>(); string[] strArray1 = requestUrl.Split('/'); string[] strArray2 = this.Url.Split('/'); if (strArray1.Length != strArray2.Length) { return false; } for (int i = 0; i < strArray2.Length; i++) { if (strArray2[i].StartsWith("{") && strArray2[i].EndsWith("}")) { variables.Add(strArray2[i].Trim("{}".ToCharArray()), strArray1[i]); } } return true; } }
一个Web应用可以采用多种不同的URL模式,所以需要注册多个继承自RouteBase的Route对象,多个Route对象组成了一个路由表。在我们自定义的迷你版ASP.NET MVC框架中,路由表通过类型RouteTable表示。RouteTable仅仅具有一个类型为RouteDictionary的Routes属性表示针对整个Web应用的全局路由表。
public class RouteTable { public static RouteDictionary Routes { get; private set; } static RouteTable() { Routes = new RouteDictionary(); } }
RouteDictionary表示一个具名的Route对象的列表,我们直接让它继承自泛型的字典类型Dictionary<string,RouteBase>,其中的Key表示Route对象的注册名称。在GetRouteData方法中,我们遍历集合找到指定的HttpContextBase对象匹配的Route对象,并得到对应的RouteData。
public class RouteDictionary : Dictionary<string, RouteBase> { public RouteData GetRouteData(HttpContextBase httpContext) { foreach (var route in this.Values) { RouteData routeData = route.GetRouteData(httpContext); if (null != routeData) { return routeData; } } return null; } }
在Global.asax中我们创建了一个基于指定路由模板的Route对象,并将其添加到通过RouteTable的静态只读属性Routes所表示的全局路由表中。
public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { RouteTable.Routes.Add("default", new Route { Url = "{controller}/{action}" }); } }
3.UrlRoutingModule
路由表的作用是对当前的HTTP请求实施路由解析,进而得到一个以Controller和Action名称为核心的路由数据,即上面介绍的RouteData对象。整个路由解析工作是通过一个类型为UrlRoutingModule的自定义IHttpModule来完成的。
public class UrlRoutingModule : IHttpModule { public void Dispose() { } public void Init(HttpApplication context) { context.PostResolveRequestCache += OnPostResolveRequestCache; } protected virtual void OnPostResolveRequestCache(object sender, EventArgs e) { HttpContextWrapper httpContext = new HttpContextWrapper(HttpContext.Current); RouteData routeData = RouteTable.Routes.GetRouteData(httpContext); if (null == routeData) { return; } RequestContext requestContext = new RequestContext { RouteData = routeData, HttpContext = httpContext }; IHttpHandler handler = routeData.RouteHandler.GetHttpHandler(requestContext); httpContext.RemapHandler(handler); } }
在实现的Init方法中,我们注册了HttpApplication的PostResolveRequestCache事件。当代表当前应用的HttpApplication对象的PostResolveRequestCache事件触发之后,UrlRoutingModule通过RouteTable的静态只读属性Routes得到表示全局路由表的RouteDictionary对象,然后根据当前HTTP上下文创建一个HttpContextWrapper对象(HttpContextWrapper是HttpContextBase的子类),并将其作为参数调用RouteDictionary对象的GetRouteData方法。
如果方法调用返回一个具体的RouteData对象,UrlRoutingModule会根据该对象本身和之前得到的HttpContextWrapper对象创建一个表示当前上下文的RequestContext对象,并将其作为参数传入RouteData的RouteHandler的GetHttpHandler方法得到一个HttpHandler对象。UrlRoutingModule最后调用HttpContextWrapper对象的RemapHandler方法对得到的HttpHandler对象进行映射,那么针对当前HTTP请求的后续处理将由这个HttpHandler来接手。
下一章我们讲解Controller的激活。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。