asp.net路由则是asp.net机制的一部分,首先理解一些常用类和对象。
1.RouteBase类。
路由系统的核心是Route对象,每一个路由注册(不同的url模式)对应的就是一个Route对象,这些Route对象注册到同一个Web应用中构成一个路由表。Route对象存储在RouteTable类里的静态属性Routes表示,这个属性返回一个RouteCollection对象。这里Route泛指继承自抽象类RouteBase的某个类型的对象
public abstract class RouteBase { //实现在GetRouteData方法中的路由解析是为了获取路由数据 public abstract RouteData GetRouteData(HttpContextBase httpContext); //GetVirtualPath方法则通过路由解析生成一个完整的虚拟路径 public abstract VirtualPathData GetVirtualPath( RequestContext requestContext,RouteValueDictionary values); //它表示是否对现有的物理文件实施路由,默认值为true,即不能通过url访问现有物理文件,只能通过路由注册表。 public bool RouteExistingFiles { get; set; } }
GetRouteData返回了一个RouteData对象,它用于封装路由数据。RouteData具有一个RouteBase的属性Route,该属性返回生成此RouteData的Route对象。其中还有
DataTokens和Values两个属性,这两个属性都返回RouteValueDictionary对象。RouteValueDictionary是一个实现了IDictionary
GetVirtualPath返回了一个VirtualPathData对象。此方法被执行时,如果定义的路由模板中的变量与指定变量列表相匹配,它会使用指定的路由变量值去替换模板里的占位符,这样就得到了虚拟路径。生成的虚拟路径与Route对象最终被封装成一个VirtualPathData对象作为返回值。这个方法里还有一个参数,类型为RequestContext。
public class RequestContext { //初始化 System.Web.Routing.RequestContext 类的新实例。 public RequestContext(); //httpContext:一个对象,该对象包含有关 HTTP 请求的信息 //routeData: 一个对象,该对象包含有关与当前请求匹配的路由的信息 public RequestContext(HttpContextBase httpContext, RouteData routeData); // 摘要: 获取有关 HTTP 请求的信息。 // 返回结果: 一个对象,该对象包含有关 HTTP 请求的信息。 public virtual HttpContextBase HttpContext { get; set; } // 摘要: 获取有关所请求路由的信息。 // 返回结果: 一个对象,该对象包含有关所请求路由的信息。 public virtual RouteData RouteData { get; set; }
2.Route类
RouteBase是一个抽象类,在ASP.NET路由系统的应用编程接口中,Route类型是其唯一的直接继承者。这个类里面有一个Url属性,它代表绑定在该路由对象上的路由模板。当请求过来时,就根据Route对象里的Url属性与请求的url匹配,这就是路由解析。
Route类型除了核心属性Url外,还有一些其他属性。Constraints为模板中的的变量设置一些约束条件,该属性类型为RouteValueDictionary,其key和Value分别为变量名和作为约束的正则表达式;Defaults同样也返回一个RouteValueDictionary对象,它保存了为路由变量定义的默认值。Route类型的DataTokens用于存储一些额外的路由变量,这些路由变量不会参与针对请求的路由解析,但对于调用Route类型的GetRouteData和GetVirtualPath方法得到的对象里的DataTokens包含的路由变量都来源于此。
3.RouteTable类
我们所说的路由注册的本质就是创建相应的Route对象并将它加入到RouteTable的静态属性Routes表示的全局路由表。先看一下RouteTable,
public class RouteTable { public static RouteCollection Routes{get;} }
可以看到RouteTable返回一个RouteCollection对象,显而易见全局路由表应该就在这个对象中。接下来看一下RouteCollection类,
public class RouteCollection : Collection{ //当我们调用下面3个方法时,会遍历集合中的每一个Route对象。针对每一个Route对象,同名的方法会被调用 //如果方法返回一个具体的RouteData或VirtualPathData对象,这个对象会直接作为方法的返回值。 //也就是说,只要遇到满足匹配的route对象,那么这个对象就会作为整个方法的返回值 //如果每个Route对象返回null,那么整个方法返回的就是null public RouteData GetRouteData(HttpContextBase httpContext); public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values); public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values); //用于注册一个路由模板使路由系统可以忽略具有这个对应模式的URL public void Ignore(string url); public void Ignore(string url, object constraints); //路由映射,一共有5个重载的MapPageRoute方法 //这个方法与Ignore方法相反,它用于注册某个路径与路由模板之间的映射,其本质就是在本集合中添加一个Route对象 public Route MapPageRoute(string routeName, string routeUrl, string physicalFile); public Route MapPageRoute(string routeName, string routeUrl, string physicalFile, bool checkPhysicalUrlAccess); }
当我们在调用MapPageRoute方法时,它会将routeName参数作为对应Route对象的注册名称,这个名称和Route对象的映射关系存在一个属性Dictionary属性里,因此我们是可以通过名称拿到这个Route对象。
4.路由注册
路由注册的核心在于根据提供的路由规则(路由模板,约束,默认值等)来创建一个Route对象,然后添加到RouteTable。先看一个例子:
public static void RegisterRoutes(RouteCollection routes) { //指定默认值 var defaults = new RouteValueDictionary { { "areacode", "010" }, { "day", "2" } }; //基于正则表达式的约束 var constains = new RouteValueDictionary { { "areacode",@"0d{2,3}" },{"day",@"[1-3]"} }; //表示对变量默认值的说明 var dataTokens = new RouteValueDictionary { { "defaultCity", "BeiJing" }, { "defaultDays", "2" } }; //注册路由 RouteTable.Routes.MapPageRoute("default", "{areacode}/{days}", "~/weather.aspx", false, defaults, constains, dataTokens); }
此时,当我们输入localhost:29323;localhost:29323/010;localhost:29323/010/2时,其效果是一样的。解析后的路由数据,如010,2等则存在RouteData.Values里面。其次,如果请求url不满足提供的正则表达式的约束,也是无法与路由对象匹配的。在constains变量中,还可以添加请求方式的约束:{"httpMethod",new HttpMethodConstraint("POST")}。
书上还强调:只有当Route.RouteExistingFiles为true,Route.RouteExistingFiles为true,Route.GetRouteData()返回匹配成功的RouteData对象时,RouteCollection.GetRouteData才能返回一个具体的RouteData对象,这个地方我还是没想明白为什么这样子。
5.根据路由规则生成url
ASP.NET路由系统主要有2个方面的应用:通过注册路由模板与物理文件路径的映射实现url和物理地址的分离;通过注册的路由规则生成完整的url;前者通过RouteCollection对象的GetRouteData方法实现,后者通过RouteCollection对象的GetVirtualPath方法实现。再来看看GetVirtualPath这个方法
//共同的参数requestContext表示请求上下文,也就是RouteData和Http上下文的封装, //values表示用于替换模板中占位符的路由变量 public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values); //name参数表示集合中具体使用的路由对象的注册名称,也是调用MapPageRoute方法时的第一个参数 public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values);
在调用这个方法时,如果没有指定存有路由变量的Route对象时,那么该方法会遍历整个路由表,直到找到一个路由模板与指定的路由参数相匹配的Route对象。具体的情况是,该方法遍历Route对象的GetVirtualPath方法,直到返回一个具体的VirtualPathData对象为止,如果每个方法都返回null,那么最后整个方法也返回null。
这个方法的参数有三种来源:Route对象中为变量定义的默认值;指定RequestContext对象的RouteData中提供的变量值(Values属性);额外提供的变量值,通过values参数指定的RouteValueDictionary对象;且这三种变量具有由低到高的选择优先级。
6.MVC里的路由
对于ASP.NET MVC应用来说,请求的目标不再是一个具体的物理地址,而是控制器里的方法。MVC里的路由是对ASP.NET进行了一个扩展,最主要的就是为RouteCollection类型定义了一系列的扩展方法,这些扩展方法是定义在RouteCollectionExtensions类里的,命名空间为System.Web.Mvc下。下面来看看这个类的一部分方法
public static class RouteCollectionExtensions { //这里与RouteCollection类里的Ignore是一样的原理 public static void IgnoreRoute(this RouteCollection routes, string url); public static void IgnoreRoute(this RouteCollection routes, string url, object constraints); //这里与RouteCollection类里的MapPageRoute是一样的原理,它还有多个重载 public static Route MapRoute(this RouteCollection routes, string name, string url); public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults); }
由于MVC模式是采用控制器,方法的方式定义路径,不是真真的物理路径与路由之间的映射,因此MapRoute和IgnoreRoute方法的参数有些变化。对于某个请求来说,如果路由表与之匹配,则匹配的Route对象的GetRouteData方法被调用并返回一个具体的RouteData对象。
7.基于Area的路由映射
对于较大规模的Web应用,我们可以采用区域的方式,每一个area都是一个独立的子系统,包含了Models、Views和controller在内的目录结构和配置文件,每个area都有自己的路由规则,基于area的路由映射通过AreaRegistration类型进行注册。
public class PersonnalMangerAreaRegistration : AreaRegistration { public override string AreaName { get { return "PersonalManger"; } } public override void RegisterArea(AreaRegistrationContext context) { context.MapRoute( "PersonnalManger_default", "PersonalManger/{controller}/{action}/{id}", new { action = "Index", id = UrlParameter.Optional }, //控制器所在命名空间 new string[1] { "PersonalManger" } ); } }
上面是我做的一个例子,这提供了如何注册区域路由的方法,接下来要了解本质学习AreaRegistration和AreaRegistrationContext类。AreaRegistration类是一个抽象类,抽象只读属性AreaName返回当前区域的名称,而抽象方法RegisterArea用于实现基于当前area的路由注册。这个类还提供了2个抽象的静态RegisterAllAreas方法:
public static void RegisterAllAreas(); public static void RegisterAllAreas(object state);//state表示传递给具体AreaRegistration的数据
当RegisterAllAreas方法被执行时,所有当前Web应用直接或间接引用的程序集会被加载(如果没有加载),MVC路由机制会从这些程序集中解析出所有继承AreaRegistration的类,并通过反射创建对应的AreaRegistration对象,同时一个作为Area注册上下文的AreaRegistrationContext 对象也被创建。不同于一般的路由注册,通过AreaRegistration实现的针对区域的路由注册具有一些细微的差异,体现在生成的DataTokens里多了2个属性,分别为area和UseNamespaceFallback属性,area代表区域的名称,后者表示是否需要使用后备的命名空间,如果显示指定了命名空间的话,则此属性为false。AreaRegistration类所生成的命名空间属性不是简单的加了一个命名空间字符串,而是还加了一个.,比如myWeb命名空间,则属性值为myWeb.*。