ASP.NET 路由系统

asp.net 的url路由系统,最初是为了实现Url与物理文件路径的分离而建立的,后来的asp.net mvc也是对asp.net路由系统的扩展,将url与物理文件映射转为url与目标controller/action的映射。

1.请求的URL与物理文件的分离

简单通过一个Demo来演示:
场景:一个页面展示员工列表,点击员工姓名显示员工详细信息

通过图片可以看出员工列表地址为:http://..../employees,当点击员工姓名链接后该员工的详细信息呈现出来了,在仔细观察其url格式为http://.../employees/{姓名}/{ID},对于熟悉asp.net mvc的童鞋肯定会知道这是其路由系统来实现的,但是URL路由系统是建立在ASP.NET上的,而非asp.net mvc 专有的,只是对它的一个扩展。
代码下载:
介绍一下路由规则:

using System;
//核心的程序集
using System.Web.Routing;

namespace WebApp
{
    public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            //定义一个路由模版,并注册到全局路由表

            //URL模版的默认值
            var defaults = new RouteValueDictionary { { "name", "*" }, { "id", "*" } };
            //核心方法:将某个物理文件映射到一个URL模版
            //本质是创建一个指定的URL模版(如下规则),然后添加到整个WebApp的全局路由表Routes
            RouteTable.Routes.MapPageRoute("", "employees/{name}/{id}", "~/Default.aspx", true, defaults); 

        }
    }
}

RouteValueDictionary在路由模版中为路由变量("{name}"和"{id}")指定默认值为*,意思是说针对"http://..../employees"的请求,路由系统会把它格式成"http://..../employees/*/*"
然后在Default.aspx页面后台代码page_load事件中来接受employee的id,这里如果请求的URL与注册的路由模版的模式匹配,就会返回一个RouteData对象,其中Values是个字典对象,它包含请求的URL的变量信息(即对应的name和id的值),如下图所示

如果是*或者null则返回所有员工与Gridview绑定,隐藏DetailsView,否则返回某员工的详细信息与DetailsView绑定,隐藏Gridview
代码:

Defualt.aspx.cs
using System;
using System.Web.UI;

namespace WebApp
{
    public partial class Default : Page
    {
        private EmployeeRepository repository;

        public EmployeeRepository Repository
        {
            get
            {
                return null == repository ? repository = new EmployeeRepository() : repository;
            }
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            if (this.IsPostBack)
            {
                return;
            }
            string employeeId = this.RouteData.Values["id"] as string;
            if (employeeId == "*" || string.IsNullOrEmpty(employeeId))
            {
                this.GridViewEmployees.DataSource = this.Repository.GetEmployees();
                this.GridViewEmployees.DataBind();
                this.DetailsViewEmployee.Visible = false;
            }
            else
            {
                var employees = this.Repository.GetEmployees(employeeId);
                this.DetailsViewEmployee.DataSource = employees;
                this.DetailsViewEmployee.DataBind();
                this.GridViewEmployees.Visible = false;
            }
        }
    }
}

 2.Route与RouteTable
上面的例子中谈到了全局路由表、路由对象等,下面具体的详细介绍
RouteBase
类型:抽象类
成员:

  1. 抽象方法GetRouteData,参数类型:HttpContextBase返回一个封装了路由信息的RouteData对象,具体的参看上面截图或msdn
    RouteData
    // 摘要:
        //     封装有关路由的信息。
        [TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
        public class RouteData
        {
            // 摘要:
            //     初始化 System.Web.Routing.RouteData 类的新实例。
            public RouteData();
            //
            // 摘要:
            //     使用指定路由和路由处理程序初始化 System.Web.Routing.RouteData 类的新实例。
            //
            // 参数:
            //   route:
            //     一个定义路由的对象。
            //
            //   routeHandler:
            //     一个处理请求的对象。
            public RouteData(RouteBase route, IRouteHandler routeHandler);
    
            // 摘要:
            //     获取在 ASP.NET 路由确定路由是否匹配请求时,传递到路由处理程序但未使用的自定义值的集合。
            //
            // 返回结果:
            //     一个包含自定义值的对象。
            public RouteValueDictionary DataTokens { get; }
            //
            // 摘要:
            //     获取或设置表示路由的对象。
            //
            // 返回结果:
            //     一个表示路由定义的对象。
            public RouteBase Route { get; set; }
            //
            // 摘要:
            //     获取或设置处理所请求路由的对象。
            //
            // 返回结果:
            //     一个处理路由请求的对象。
            public IRouteHandler RouteHandler { get; set; }
            //
            // 摘要:
            //     获取路由的 URL 参数值和默认值的集合。
            //
            // 返回结果:
            //     一个对象,其中包含根据 URL 和默认值分析得出的值。
            public RouteValueDictionary Values { get; }
    
            // 摘要:
            //     使用指定标识符检索值。
            //
            // 参数:
            //   valueName:
            //     要检索的值的键。
            //
            // 返回结果:
            //     其键与 valueName 匹配的 System.Web.Routing.RouteData.Values 属性中的元素。
            //
            // 异常:
            //   System.InvalidOperationException:
            //     valueName 的值不存在。
            public string GetRequiredString(string valueName);
        }

    Values和DataTokens两个属性都是RouteValueDictionary类型的对象,RouteValueDictionary是一个用于保存路由变量的字典,其中Key和Value分别表示变量的名称和值。
    RouteData的Values属性的变量是路由对象通过对请求的URL解析得到的。
    DataTokens属性则是直接附加到路由对象上的自定义变量。
    GetRequiredString方法用于获取指定名称的变量值,例如获取MVC中controller或action的值
    RouteHandler:实现url和物理文件映射的核心对象,用于处理请求的HttpHandler对象,当成功请求到某个.aspx页面后,通过匹配路由对象的GetRouteData方法得到的RouteData对象被直接附加到目标页面对应的Page对象上

    public class Page:TempateControl,IHttpHandler
    {
           //其他成员public RouteData RouteData { get; }    
    }
  2. 抽象方法GetVirtualPath,参数类型:RequestContext 返回一个封装了Url的VirtualPathData对象
    当请求的URL与URL模版匹配,则将URL中的变量替换URL模版中的变量占位符生成一个虚拟路径,虚拟路径和路由对象被封装成一个VirtualPathData对象返回
    public class VirtualPathData
        {
            public VirtualPathData(RouteBase route, string virtualPath);
            //获取路由定义的自定义值集合
            public RouteValueDictionary DataTokens { get; }
            //获取或设置用于创建 URL 的路由
            public RouteBase Route { get; set; }
            //获取或设置依据路由定义创建的 URL。
            public string VirtualPath { get; set; }
        }

    RequestContext是URL路由系统和ASP.NET MVC路由体系中使用最频繁的类型,用于表示当前请求上下文,实际上是对HTTP上下文和RouteData的封装

    public class RequestContext
        {
            public RequestContext();
            public RequestContext(HttpContextBase httpContext, RouteData routeData);
            //获取有关 HTTP 请求的信息
            public virtual HttpContextBase HttpContext { get; set; }
            //获取有关所请求路由的信息
            public virtual RouteData RouteData { get; set; }
        }

Route
 是抽象类RouteBase唯一的直接子类

Route
public class Route : RouteBase
    {
        //==构造函数==
        //使用指定的 URL 模式和处理程序类初始化 Route 类的新实例
        public Route(string url,IRouteHandler routeHandler);
        //使用指定的 URL 模式、默认参数值和处理程序类初始化 Route 类的新实例。
        public Route(string url,RouteValueDictionary defaults,IRouteHandler routeHandler);
        //使用指定的 URL 模式、默认参数值、约束和处理程序类初始化 Route 类的新实例
        public Route(string url,RouteValueDictionary defaults,RouteValueDictionary constraints,IRouteHandler routeHandler);
        //使用指定的 URL 模式、默认参数值、约束、自定义值和处理程序类初始化 Route 类的新实例。
        public Route(string url,RouteValueDictionary defaults,RouteValueDictionary constraints,RouteValueDictionary dataTokens,IRouteHandler routeHandler);
        
        //==常用方法==
        //返回有关所请求路由的信息
        public override RouteData GetRouteData(HttpContextBase httpContext);
        //返回与路由关联的 URL 的相关信息
        public override VirtualPathData GetVirtualPath(RequestContext requestContext,RouteValueDictionary values);

        //==属性==
        //获取或设置要在 URL 不包含所有参数时使用的值。
        public RouteValueDictionary Defaults { get; set; }
        //获取或设置为 URL 参数指定有效值的表达式的词典
        public RouteValueDictionary Constraints { get; set; }
        //获取或设置传递到路由处理程序但未用于确定该路由是否匹配 URL 模式的自定义值。
        public RouteValueDictionary DataTokens { get; set; }
        //获取或设置一个值,该值指示 ASP.NET 路由操作是否应处理与现有文件匹配的 URL。
        public bool RouteExistingFiles { get; set; }
        //获取或设置处理路由请求的对象。
        public IRouteHandler RouteHandler { get; set; }
        //获取或设置路由的 URL 模式。
        public string Url { get; set; }
    }

演示如何创建 Route 对象并将其添加到 Routes 属性中

Demo
void Application_Start(object sender, EventArgs e) 
{
    RegisterRoutes(RouteTable.Routes);
}

public static void RegisterRoutes(RouteCollection routes)
{
    routes.Add(new Route
    (
         "Category/{action}/{categoryName}"
         , new CategoryRouteHandler()
    ));
}

 RouteTable
一个Web应用通过RouteTable的静态只读属性Routes维护一个全局的路由表

public class RouteTable
    {
        public static RouteCollection Routes { get; }
    }
RouteCollection
 public class RouteCollection : Collection<RouteBase>
    {
        //其他成员
        //返回有关集合中与指定值匹配的路由的信息
        public RouteData GetRouteData(HttpContextBase httpContext);
        //如果具有指定的上下文和参数值,则返回与路由关联的 URL 路径的相关信息
        public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
        //如果具有指定的上下文、路由名称和参数值,则返回与命名路由关联的 URL 路径的相关信息
        public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values);
        //定义不需检查是否匹配路由的 URL 模式
        public void Ignore(string url);

        /***eg:
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            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);
        }
        */
        //定义一个 URL 模式,此模式在请求 URL 满足指定约束的情况下不需要检查 URL 是否与路由匹配
        public void Ignore(string url,Object constraints);
        //eg:routes.Ignore("{*allaspx}", new {allaspx=@".*\.aspx(/.*)?"});
        //提供用于定义 Web 窗体应用程序的路由的方法
        public Route MapPageRoute(
                    string routeName,
                    string routeUrl,
                    string physicalFile,
                    bool checkPhysicalUrlAccess,
                    RouteValueDictionary defaults,
                    RouteValueDictionary constraints,
                    RouteValueDictionary dataTokens
                );
        /*
         * routes.MapPageRoute("ExpenseDetailRoute",
                        "ExpenseReportDetail/{locale}/{year}/{*queryvalues}", "~/expenses.aspx",
                        false,
                        new RouteValueDictionary 
                            { { "locale", "US" }, { "year", DateTime.Now.Year.ToString() } },
                        new RouteValueDictionary 
                            { { "locale", "[a-z]{2}" }, { "year", @"\d{4}" } },
                        new RouteValueDictionary 
                            { { "account", "1234" }, { "subaccount", "5678" } });

         */
    }

对现有文件路由

注册路由:

protected void Application_Start(object sender, EventArgs e)
        {
            var defaults = new RouteValueDictionary { { "areacode", "010" }, { "days", 2 }};
            var dataTokens = new RouteValueDictionary { { "defaultCity", "BeiJing" }, { "defaultDays", 2 } };
            RouteTable.Routes.MapPageRoute("default", "{areacode}/{days}","~/weather.aspx", false, defaults, null, dataTokens);
        }

weather页面用来展示当前RouteData的各项属性,当直接请求http://.../weather.aspx时,页面的RouteData各项属性都为空,所以ASP.NET没有对请求地址实施路由
解决方案:在注册路有前加上一句:RouteTable.Routes.RouteExistingFiles=true;这句话是说需要对存在的物理文件实施路由,也就是说如果请求的URL与某个物理文件的路径地址一致的情况下是否还需要对其实施路由,该属性默认值为false,

注册路由忽略地址

但问题出现了,我们在页面weather.aspx引用了样式Style.css,采用RouteExistingFiles=true,意味着Style.css访问也会被路由,然后导向weather.aspx这个页面,这样一来无法正常在页面引用js和css文件了。
解决方案:
在注册路由之前加上一句:RouteTable.Routes.Ignore("{filename}.css/{*pathInfo}");,但是这句话必须在路由注册之前执行。

直接添加路由对象

我们调用RouteCollection对象的MapPageRoute方法进行路由注册的本质是在路由字典中添加Route,所以我们可以自己手工Add一个Route对象,

//路由注册方式1
          routes.MapPageRoute("ExpenseDetailRoute",
                        "ExpenseReportDetail/{locale}/{year}/{*queryvalues}", "~/expenses.aspx",
                        false,
                        new RouteValueDictionary 
                            { { "locale", "US" }, { "year", DateTime.Now.Year.ToString() } },
                        new RouteValueDictionary 
                            { { "locale", "[a-z]{2}" }, { "year", @"\d{4}" } },
                        new RouteValueDictionary 
                            { { "account", "1234" }, { "subaccount", "5678" } });

         //路由注册方式2
        Route route=new Route("ExpenseReportDetail/{locale}/{year}/{*queryvalues}",
                                new RouteValueDictionary 
                            { { "locale", "US" }, { "year", DateTime.Now.Year.ToString() } },
                                new RouteValueDictionary 
                            { { "locale", "[a-z]{2}" }, { "year", @"\d{4}" } },
                                new RouteValueDictionary 
                            { { "account", "1234" }, { "subaccount", "5678" } },
                            new PageRouteHandler("~/expenses.aspx",false));
        RouteTable.Routes.Add("default",route);

2.根据路由规则生成URL

ASP.NET的路由系统主要有两个方面的应用

  1. 通过注册URL模版与物理文件的匹配实现请求地址和物理地址的分离
  2. 通过注册的路由规则生成一个响应的URL,通过RouteCollection的GetVirtualPath方法来实现
    public class RouteCollection : Collection<RouteBase>
    {
        //其他成员..

        //如果具有指定的上下文和参数值,则返回与路由关联的 URL 路径的相关信息
        public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
        //如果具有指定的上下文、路由名称和参数值,则返回与命名路由关联的 URL 路径的相关信息
        public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values);
    }

GetVirtualPath方法
如果requestContext参数为null,这种情况会基于当前HTTP上下文(对应HttpContext的静态属性Current)创建一个RequestContext对象作为GetVirtualPath的参数。如果RouteData为空,则抛出异常
路由对象针对GetVirtualPath方法而进行的路由匹配只要求URL模版中定义的变量的值都能被提供,这些变量值具有3个来源:

  1. 路由对象定义的默认变量值
  2. 指定RequestContext的RouteData提供的变量值(Values属性)
  3. 手工提供的变量值(通过values参数指定的RouteValueDictionary对象)
    public class RouteCollection : Collection<RouteBase>
    {
        //其他成员..

        //如果具有指定的上下文和参数值,则返回与路由关联的 URL 路径的相关信息
        public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
        //如果具有指定的上下文、路由名称和参数值,则返回与命名路由关联的 URL 路径的相关信息
        public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values);  
    }
        protected void Page_Load(object sender,EventArgs e)
        {
            //1.指定RequestContext的RouteData提供的Values属性
            RouteData routeData =new RouteData();
            routeData.Values.Add("areaCode","010");
            routeData.Values.Add("days","2");
            //封装到RequestContext中
            RequestContext requestContext=new RequestContext();
            //包装当前HttpContext
            requestContext.HttpContext =new HttpContextWrapper(HttpContext.Current);
            requestContext.RouteData=routeData;
            
            //2.手工指定参数values
            RouteValueDictionary values=new RouteValueDictionary();
            values.Add("areaCode","028");
            values.Add("days","3");

            Reponse.Write(RouteTable.Routes.GetVirtualPath(null,null).VirtualPath);//输出注册路有时指定的默认路由值

            Reponse.Write(RouteTable.Routes.GetVirtualPath(requestContext,null).VirtualPath);//输出:/010/2

            Reponse.Write(RouteTable.Routes.GetVirtualPath(requestContext,values).VirtualPath);//输出:/028/3
        }
posted @ 2013-01-25 11:48  hailiang2013  阅读(355)  评论(0编辑  收藏  举报