我们先了解一下 System.Web.Routing 中的几个类型。
- UrlRoutingModule : IHttpModule : 入口点。订阅 HttpApplication 相关事件,修改 HttpContext.Handler (IHttpHandler),使得 MvcHandler 得以执行。
- Route : RouteBase : 路径转向规则,包括路径匹配表达式(Url)、参数默认值(Defaults)、请求处理程序(RouteHandler) 等。
- RouteCollection : Collection<RouteBase> : 存储所有的 Route 规则的集合,提供依据上下文获取动态路由数据的方法。
- RouteTable : 持有全局 RouteCollection 实例引用的一个辅助类。
- RouteData : 依据当前上下文进行解析的动态请求路由信息,包括从请求参数或路由规则中提取的 Controller Name、Action Name、Action Method Parameter 等。
- IRouteHandler : Route 中请求处理程序的包装接口。
要使用 Routing,除了在配置文件中添加 UrlRoutingModule 外,我们还得在 Global.asax.cs 中注册相应的路由规则(Route)。
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Test",
"Home/Test/{x}/{y}",
new { controller = "Home", action = "Test", x = 100, y = 200 }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
System.Web.Mvc 额外提供了几个扩展方法来简化我们注册 Route 规则。
{
public static void IgnoreRoute(this RouteCollection routes, string url)
{
routes.IgnoreRoute(url, null);
}
public static void IgnoreRoute(this RouteCollection routes, string url, object constraints)
{
// … 省略部分代码 …
Route route = new Route(url, new StopRoutingHandler())
{
Constraints = new RouteValueDictionary(constraints)
};
routes.Add(route);
}
public static void MapRoute(this RouteCollection routes, string name, string url)
{
routes.MapRoute(name, url, null, null);
}
public static void MapRoute(this RouteCollection routes, string name, string url, object defaults)
{
routes.MapRoute(name, url, defaults, null);
}
public static void MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints)
{
// … 省略部分代码 …
Route route = new Route(url, new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints)
};
if (string.IsNullOrEmpty(name))
{
routes.Add(route);
}
else
{
routes.Add(name, route);
}
}
}
有一点需要注意,System.Web.Routing 通过循环查找的方式来匹配路由规则,因为我们要按以下顺序注册路由规则。
(1) 忽略规则 (IgnoreRoute)。
(2) 具体匹配规则。
(3) 通用匹配规则。
RouteTable.Routes 实际上指向一个应用程序域中唯一的 RouteCollection 实例。
{
// Fields
private static RouteCollection _instance = new RouteCollection();
// Properties
public static RouteCollection Routes
{
get { return _instance; }
}
}
RouteCollection 除了保存所有路由规则 (Route) 外,还有几个重要的方法。
{
// Methods
public void Add(string name, RouteBase item);
public RouteData GetRouteData(HttpContextBase httpContext);
public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values);
// Properties
public RouteBase this[string name] { get; }
public bool RouteExistingFiles { get; set; }
}
GetRouteData() 通过当前请求上下文,找出最合适的路由规则(Route),并生成当前执行环境所需的动态路由数据(RouteData)。
{
public RouteData GetRouteData(HttpContextBase httpContext)
{
// … 省略部分代码 …
using (this.GetReadLock())
{
foreach (RouteBase base2 in this)
{
RouteData routeData = base2.GetRouteData(httpContext);
if (routeData != null)
{
return routeData;
}
}
}
return null;
}
}
循环调用搜索规则的 GetRouteData 方法来获取动态路由数据,这就是上面我们提到要注意路由规则注册顺序的原因。
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) +
httpContext.Request.PathInfo;
RouteValueDictionary values = this._parsedRoute.Match(virtualPath, this.Defaults);
if (values == null)
{
return null;
}
RouteData data = new RouteData(this, this.RouteHandler);
if (!this.ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest))
{
return null;
}
foreach (KeyValuePair<string, object> pair in values)
{
data.Values.Add(pair.Key, pair.Value);
}
if (this.DataTokens != null)
{
foreach (KeyValuePair<string, object> pair2 in this.DataTokens)
{
data.DataTokens[pair2.Key] = pair2.Value;
}
return data;
}
}
}
Route.GetRouteData() 首先调用 ParsedRoute.Match() 来检查路径是否匹配,接下来使用 ProcessConstraints() 检查相应的约束规则,如果这些检查都得以通过,则生成最终的动态路由数据。RouteCollection.GetRouteData() 在获取该结果后终止循环。
IRouteHandler 的作用是返回一个特定 IHttpHandler,比如 MvcHandler,当然也可以是 "System.Web.UI.Page : IHttpHandler"。
{
IHttpHandler GetHttpHandler(RequestContext requestContext);
}
public class MvcRouteHandler : IRouteHandler
{
protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MvcHandler(requestContext);
}
IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
{
return this.GetHttpHandler(requestContext);
}
}
UrlRoutingModule 的执行顺序,可参考《ASP.NET MVC Preview 2 – 流程分析 (1)》。
Route 的相关语法规则,可参考《ASP.NET MVC Preview 3 Release 》(ScottGu 中文版)。