现在的位置: 首页 > 综合 > 正文

ASP.NET MVC路由扩展:路由映射

2012年08月14日 ⁄ 综合 ⁄ 共 6897字 ⁄ 字号 评论关闭

上周我写了三篇文章()详细地介绍了ASP.NET的路由系统。ASP.NET的路由系统旨在通过注册URL模板与物理文件之间的映射进而实现请求地址与文件路径之间的分离,但是对于ASP.NET MVC应用来说,请求的目标不再是一个具体的物理文件,而是定义在某个Controller类型中的Action方法。出于自身路由特点的需要,ASP.NET对ASP.NET的路由系统进行了相应的扩展。

目录
一、基本路由映射
二、实例演示:注册路由映射与查看路由信息
三、基于Area的路由映射
    1、AreaRegistration与AreaRegistrationContext 
    2、AreaRegistration的缓存
    3、实例演示:查看基于Area路由信息

一、基本路由映射

通过前面的介绍我们知道基于某个物理文件的路由映射通过调用代表全局路由表的RouteTable的静态属性Routes(一个RouteCollection对象)的MapPageRoute方法来完成,为了实现针对目标Controller和Action的路由,ASP.NET MVC针对RouteCollection类型定义了一系列的扩展方法以实现文件路径无关的路由映射,这些扩展方法定义在RouteCollectionExtensions类型中。如下面的代码片断所示,RouteCollectionExtensions定义了两组方法,方法IgnoreRoute用于注册不需要进行路由的URL模板,对应于RouteCollectionExtensions的Ignore方法;仿佛MapRoute用于进行基于URL模板的路由注册,对应于RouteCollectionExtensions的MapPageRoute方法。

   1: public static class RouteCollectionExtensions

   2: {

   3:     //其他成员   

   4:     public static void IgnoreRoute(this RouteCollection routes, string url);    

   5:     public static void IgnoreRoute(this RouteCollection routes, string url, object constraints);   

   6:  

   7:     public static Route MapRoute(this RouteCollection routes, string name, string url);    

   8:     public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults);    

   9:     public static Route MapRoute(this RouteCollection routes, string name, string url, string[] namespaces);    

  10:     public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints);    

  11:     public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, string[] namespaces);    

  12:     public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces);

  13: }

由于ASP.NET MVC的路由注册与具体的物理文件无关,所以MapRoute方法中并没有一个表示文件路径的physicalFile参数。与直接定义在RouteCollectionExtensions中的Ignore和MapPageRoute方法不同的是,表示默认变量的参数defaults和基于正则表达式的变量约束的参数constraints都不再是一个RouteValueDictionary对象,而是一个普通的object。这主要是为了编程上的便利,使得我们可以通过匿名类型的方式来指定这两个参数值。该方法在内部会通过反射的方式得到指定对象所有属性值,并转换为RouteValueDictionary对象,其属性名和属性值作为字典元素的Key和Value。

对于ASP.NET MVC来说,最终需要通过在请求地址中指定的Controller名称来创建具体的Controller实例。由于Controller名称 仅仅对应着类型的名称,Controller的成功实例化的前提是我们能够正确地解析出它的具体类型,所以我们需要使用了命名空间。在调用MapRoute方法的时候我们可以通过字符串数组类型的参数namespaces来指定一个命名空间的列表。对于注册的命名空间,可以指定一个代表完整命名空间的字符串,也可以使用“*”作为通配符。

添加的命名控件列表最终是被存储于Route对象的DataTokens属性中,对应的Key为“Namespaces”。MapRoute方法没有为初始化Route对象的DataTokens属性提供相应的参数,如果没有指定命名空间列表,所有通过该方法添加的Route对象的DataTokens属性总是一个空的RouteValueDictionary对象。

对于针对定义在某个Controller中的某个Action的请求,如果注册的路由表与之匹配,具体匹配的某个路由对象的GetRouteData被调用并返回一个具体的RouteData对象。根据对请求地址进行解析得到的目标Controller和Action的名称必须包含在该RouteData的Values属性对应的RouteValueDictionary对象中,其对应的Key分别为controlleraction

二、 实例演示:注册路由映射与查看路由信息

ASP.NET MVC通过定义在RouteCollectionExtensions中的扩展方法MapRoute进行路由映射,为了让读者对此有一个深刻的认识,我们来进行一个简单的实例演示。我们依然沿用之前关于获取天气信息的场景,看看通过这种方式进行注册的Route对象针对匹配的HTTP请求返回怎样的RouteData对象。[源代码从这里下载]

我们在创建的ASP.NET Web应用(不是ASP.NET MVC应用)添加一个Web页面(Default.aspx),并按照之前的方式以内联代码的方式直接将RouteData的相关属性显示出来,页面主体部分的HTML如下所示。需要注意的是我们显示的RouteData是从定义的方法GetRouteData方法获取的,而不是对应于当前页面的RouteData属性。

   1: <body>

   2:     <form id="form1" runat="server">

   3:     <div>

   4:         <table>

   5:             <tr>

   6:                 <td>Route:</td>

   7:                 <td><%=GetRouteData().Route != null? GetRouteData().Route.GetType().FullName:"" %></td>

   8:             </tr>

   9:             <tr>

  10:                 <td>RouteHandler:</td>

  11:                 <td><%=GetRouteData().RouteHandler != null? GetRouteData().RouteHandler.GetType().FullName:"" %></td>

  12:             </tr>

  13:             <tr>

  14:                 <td>Values:</td>

  15:                 <td>

  16:                     <ul>

  17:                         <%foreach (var variable in GetRouteData().Values)

  18:                           {%>

  19:                         <li><%=variable.Key%>=<%=variable.Value%></li>

  20:                         <% }%>

  21:                     </ul>

  22:                 </td>

  23:             </tr>

  24:             <tr>

  25:                 <td>DataTokens:</td>

  26:                 <td>

  27:                     <ul>

  28:                         <%foreach (var variable in GetRouteData().DataTokens)

  29:                           {%>

  30:                         <li><%=variable.Key%>=<%=variable.Value%></li>

  31:                         <% }%>

  32:                     </ul>

  33:                 </td>

  34:             </tr>

  35:         </table>

  36:     </div>

  37:     </form>

  38: </body>

我们将GetRouteData方法定义在当前页面的后台代码中。如下面的代码片断所示,我们手工创建了一个HttpRequest和HttpResponse对象,HttpRequest的请求的地址为“http://localhost:3721/0512/3”(3721是本Web应用对应的端口号)。根据这两个对象创建了HttpContext对象,并以此创建一个HttpContextWrapper对象。最终我们将其作为参数调用RouteTable的Routes属性的GetRouteData方法并返回。这个方法实际上就是模拟注册的路由表针对相对地址为“/0512/3”的HTTP请求的路由处理。

   1: public partial class Default : System.Web.UI.Page

   2: {

   3:     private RouteData routeData;

   4:     public RouteData GetRouteData()

   5:     {

   6:         if (null != routeData)

   7:         {

   8:             return routeData;

   9:         }

  10:         HttpRequest request = new HttpRequest("default.aspx", "http://localhost:3721/0512/3", null);

  11:         HttpResponse response = new HttpResponse(new StringWriter());

  12:         HttpContext context = new HttpContext(request, response);

  13:         HttpContextBase contextWrapper = new HttpContextWrapper(context);

  14:         return routeData = RouteTable.Routes.GetRouteData(contextWrapper);

  15:     }

  16: }

具体的路由映射依然定义在添加的Global.asax文件中。如下面的代码片断所示,我们通过调用RouteTable的Routes属性的MapRoute方法注册了一个采用“{areacode}/{days}”作为URL模板的路由对象,并指定了默认变量、约束和命名空间列表。

   1: public class Global : System.Web.HttpApplication

   2: {

   3:     protected void Application_Start(object sender, EventArgs e)

   4:     {

   5:         object defaults = new { areacode = "010", days = 2, defaultCity="BeiJing", defaultDays=2};

   6:         object constraints = new { areacode = @"0\d{2,3}", days = @"[1-3]{1}"};

   7:         string[] namespaces = new string[] { "Artech.Web.Mvc", "Artech.Web.Mvc.Html" };

   8:         RouteTable.Routes.MapRoute("default", "{areacode}/{days}", defaults, constraints, namespaces);

   9:     }               

  10: }

如果我们现在在浏览器中访问Default.aspx页面,会得到下图所示的结果,从中我们可以得到一些有用的信息:

  • 与调用RouteCollection的MapPateRoute方法进行路由映射不同的是,这个得到的RouteData对象的RouteHandler属性是一个System.Web.Mvc.MvcRouteHandler对象。
  • 在MapRoute方法中通过defaults参数指定的两个与URL匹配无关的变量(defaultCity=BeiJing;defaultDays=2)体现在RouteData的Values属性中。这意味着如果我们没有在URL模板中为Controller和Action的名称定义相应的变量({controller}和{action}),也可以将它们定义成默认变量。
  • DataTokens属性中包含一个Key值为Namespaces值为字符数组的元素,我们不难猜出它对应着我们指定的命名空间列表。

clip_image002

三、基于Area的路由映射

对于一个较大规模的Web应用,我们可以从功能上通过Area将其划分为较小的单元。每个Area相当于一个独立的子系统,具有一套包含Models、Views和Controller在内的目录结构和配置文件。一般来说,每个Area具有各自的路由规则(URL模版上一般会体现Area的名称),而基于Area的路由映射通过AreaRegistration进行注册。

AreaRegistration与AreaRegistrationContext

基于Area的路由映射通过AreaRegistration进行注册。如下面的代码片断所示,AreaRegistration是一个抽象类,抽象只读属性AreaName返回当前Area的名称,而抽象方法RegisterArea用于实现基于当前Area的路由注册。

   1: public abstract class AreaRegistration

   2: {    

   3:     public static void RegisterAllAreas();

   4:     public static void RegisterAllAreas(object state);

   5:  

   6:     public abstract void RegisterArea(AreaRegistrationContext context);

   7:     public abstract string AreaName { get; }

   8: }

AreaRegistration定义了两个抽象的静态RegisterAllAreas方法重载,参数state用于传递给具体AreaRegistration的数据。当RegisterAllArea方法执行的时候,它先遍历通过BuildManager的静态方法GetReferencedAssemblies方法得到的编译Web应用所使用的程序集,通过反射得到所有实现了接口IController的类型,并通过反射创建相应的AreaRegistration对象。对于每个AreaRegistration对象,一个AreaRegistrationContext对象被创建出来并作为参数调用它们的RegisterArea方法。

如下面的代码片断所示,AreaRegistrationContext的只读属性AreaName表示Area的名称,属性Routes是一个代表路由表的RouteCollection对象,而State是一个用户自定义对象,它们均通过构造函数进行初始化。具体来说,对于最初通过调用AreaRegistration的静态方法RegisterAllAreas创建的AreaRegistrationContext对象,AreaName来源于当前AreaRegistration对象的同名属性,Routes则对应着RouteTable的静态属性Routes表示的全局路由表,而在调用RegisterAllAreas方法指定的参数(state)作为AreaRegistrationContext对象的State参数。

   1: public class AreaRegistrationContext

   2: {    

   3:     public AreaRegistrationContext(string areaName, RouteCollection routes);

   4:     public AreaRegistrationContext(string areaName, RouteCollection routes, object state);

   5:  

   6:     public Route MapRoute(string name, string url);

   7:     public Route MapRoute(string name, string url, object defaults);

抱歉!评论已关闭.