asp.net 路由重写url机制学习详解

作者:袖梨 2022-06-25

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接口的字典,用来保存路由变量。存储于Values和DataTokens的差别是:Values是解析请求url得到的;DataTokens是直接附加到路由对象上的自定义变量。RouteData类里还有一个非常重要的属性:RouteHandler,它在整个路由系统中具有重要的地位,因为最终用于处理请求的HttpHandler对象由它来提供

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.*。

相关文章

精彩推荐