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
代码:
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
类型:抽象类
成员:
- 抽象方法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; } }
- 抽象方法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唯一的直接子类
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 属性中
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; } }
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的路由系统主要有两个方面的应用
- 通过注册URL模版与物理文件的匹配实现请求地址和物理地址的分离
- 通过注册的路由规则生成一个响应的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个来源:
- 路由对象定义的默认变量值
- 指定RequestContext的RouteData提供的变量值(Values属性)
- 手工提供的变量值(通过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 }