一、网址路由

      1.1  比对通过浏览器传来的HTTP请求

         客户端对ASP.NET网站发出请求时,能通过R偶汤尼盖找到适当的HttpHandler来处理网页,大致的流程如图:

         如果HttpHandler是由MvcHandler来处理,那么,此时就会进入MVC的执行生命周期,并且会找到适当的Controller与Action来对其进行处理,并将信息反馈给客户端。

      1.2 将适当的网址返回浏览器

        网址路由的另一个用途是决定MVC 应该输出什么样的网址并将其返回浏览器,跳转地址或在View中显示超链接时,都需要参考网址路由的定义,因为这样才能动态决定MVC输出网址应该是什么。

     1.3  默认网址路由

        Global.asax已经定义了两个默认的网址路由,参考图如下:

      ① 所有ASP.NET Web应用程序执行的入口都是HttpApplication的Application_Start()事件,所有的MVC Routing都会在此定义。其中,RoutTable.Routes是一个公开的静态对象,用于存储所有Routing的规则集(RouteCollection类)。

      默认RegisterRoutes()方法中的IgnoreRoute()辅助方法用于定义不需要通过Routing处理的网址。

     "{resource}"代表一个名为"resource"的Route Valueg表达式,但其实这里取任何名字都可以,它只是代表一个变量空间(PlaceHolder类),总之就是代表一个位置,用于放置一个用不到的变量。

      "{*pathInfo}"代表一个名称为"pathInfo"的RouteValue表达式,名称前面的星号(*)的意思是"CatchAll"(抓到全部)。这个名为"pathInfo"的RouteValue表达式的值是完整的路径信息(Path Info)中扣除在中比对到的剩余的网址。例如:网址是"/TEST.axd/a/b/c/d",则"{PathInfo}"的值为"a/b/c/d",如果没有加上星号,"{PathInfo}"的值应为"a"。其实在这里取任何名字都可以,因为它只代表一个变量的位置。

     MapRoute()是最常用来定义Routing规则的辅助方法。

     定义Route的名称,在此为"Default"。

     定义网址格式和每个网址段落(PathSegment)的RouteValue表达式名称。

TIP : 该网址不能以斜线(/)开头。

     定义各RouteValue表达式的默认值,当网址路由比对不到HTTP请求网址时,就会改以默认值替代。

二、HTTP请求的URL如何对应网址路由

      由于默认定义了两个网址路由,按照ASP.NET Routing的规则,当HTTP提出请求后,URL会进行网址路由的比对,而且是由上而下地一条一条比对,直到比对到符合HTTP请求的网址为止。

   2.1 网址路由范例

      下面举例,弄清URL和Routing之间更多概念上的联系。

     [1]. http://localhost/Trace.axd/a/b/c/d/e
TIP :所有的网址都是从"http://localhost/"之后开始比对的!

  • 比对顺序

    (1)比对routes.IgnoreRoute命名空间的"{resource}.axd/{*pathInfo}"网址格式。

    (2)"{resource}.axd"比对到"Trace.axd",因此继续比对下一个RouteValue表达式。

    (3)比对"{*pathInfo}",得到"a/b/c/d/e"。

    (4)因为所有的RouteValue表达式都比对成功,所以该HTTP请求会由此网址路由提供服务。

  • 比对结果

     该网址使用routes.IgnoreRoute命名空间进行处理,即,MVC为忽略此请求,改为ASP.NET架构本身继续进行处理。

    [2] http://localhost/Trade.axd

  •  比对顺序

    (1)比对routes.IgnoreRoute命名空间的"{resource}.axd/{*pathInfo}"网址格式。

    (2)"{resource}.axd"比对到"Trace.axd",因此继续比对下一个RouteValue表达式。

    (3)比对"{*pathInfo}",由于请求的部分已经没有数据,所以理论上不会比对到任何结果。但由于"{*pathInfo}"属于CallAll,此语法会比对到包括空字符串在内的全部内容,所以这个部分也算比对成功,只是"{*pathInfo}"为空而已。

   (4)因为所有的RouteValue表达式都比对成功,所以该HTTP请求会由此网址路由提供服务。

  • 比对结果

     该网址使用routes.IgnoreRoute命名空间进行处理,即,MVC为忽略此请求,改为ASP.NET架构本身继续进行处理。

     [3] http://localhost/Member/Detail?id=123

  • 比对顺序

    (1)比对routes.IgnoreRoute命名空间的"{resource}.axd/{*pathInfo}"网址格式。

    (2)比对请求URL的的一部分,即"Member",由于没有比对到".axd",所以比对失败。

    (3)跳转到routes.MapRoute命名空间的"{controller}/{action}/{id}"网址格式。

    (4)比对请求URL的第一部分,即"Member",并且比对到{controller}参数。

    (5)比对请求URL的第二部分,即"Detail",并且比对到"{action}"参数。

    (6)接下来的"?id=123"就不算是网址的一部分了,所以它不会被算进RouteValue表达式中,因此不会在对此比对了。

    (7)"{id}"部分因为没有比对到,所以会读取默认值,也就是"UrlParameter.Optional"部分。由于存在默认值,所以也算比对成功。

    (8)因为所有的RouteValue表达式都比对成功,所以该HTTP请求会由此网址路由提供服务。

  • 比对结果

     该网址使用routes.MapRoute命名空间进行处理,并调过MvcHandler将值赋予适当的Controller和Action程序。在这里会对应MemberController的Detail动作。

TIP :在"URL及参数"位置出现的所有路由参数都是必要的参数,必须完全符合必读规则才能比对成功;如果比对失败,就会调至下一条网址路由规则继续比对。

   2.2 为网址路由加上限制条件

     MapRoute()是最常用来定义Routing规则的辅助方法,它有许多应用方式(重载)。一个常用的应用方式为"样式比对规则(正则表达式)+限制条件"。

1            context.MapRoute(
2 
3                  "Order_default",
4                  "Order/{controller}/{action}/{id}",
5                  new { action = "Index", id = UrlParameter.Optional },
6                  new { id = @"\d+" }
7             );

      我们在MapRoute()辅助方法中设置了4个参数,这些参数指定了一个匿名对象,其中的id属性就是我们比对{id}路由值的限制条件,其限制条件是用正则表达式(Regular Expression)来表示的。"\id+"代表比对到的{id}路由值时必须为数字才算比对成功,而这就是限制条件。

      若网址为"http://localhost/Order/Member/Index/123ABC.",由于此对到的{id}路由值不符合限制条件,所以这个网址就算比对失败,接着会自动跳到下一个网址路由规则继续比对。

TIP : 这里定义的正则表达式默认是完全比对。如果你定义的样式为"\id+',事实上,在比对时会转换为"^\d+$"。

三、网址路由如何在MVC中生成网址。

      URL是如何比对网址路由是网址路由的一个功能,另一个主要功能是在Controller或View中依据网址路由的定义生成适当的网址。下面介绍如何使用RouteTable.Routes.GetVirtualPath命名空间取得动态网址。

     先用默认的MVC项目模块进行测试。打开网址http://localhost/Home/About,此请求所得到的路由值如图:

字段

controller

Home

action

About

id

UrlParameter.Optional

    在"/Views/Home/About.aspx"页面中添加以下程序代码。

1        <%=
2             RouteTable.Routes.GetVirtualPath(
3                  Request.RequestContext,
4                  new RouteValueDictionary(new { page=1})
5             ).VirtualPath        
6         %>

     可以看到:在RouteTable.Routes.GetVirtualPath命名空间中,第一个参数为Request.RequestContext,在会输入当前的请求信息,包括RouteValue、QueryString和其他完整的请求;第二个参数中多输入了一个RouteValueDictionary对象,并插入了一个{page}路由值。因此,在获取网址路由之前会先合并出一组路由值,如表所示:

字段

Action

About

Id

UrlParameter.Optional

Page

1

      最后,MVC会使用这组新的路由值由上而下——比对路由表(RouteTable)中所有的路由规则,已得到最适合的路由规则,并产生适当的网址。

      拿以下路由规则来说,其中定义了3个路由参数,而我们得到的4个路由值中有3个路由值完全符合定义,所以,此网址路由会被选中,并且MVC会以此路由定义好的格式产生网址。

 1           routes.MapRoute(
 2                 "Default",                     // 路由名称
 3                 "{controller}/{action}/{id}",  // 带有参数的 URL
 4                 new
 5                 {                              // 参数默认值 
 6                     controller = "Home",
 7                     action = "Index",
 8                     id = UrlParameter.Optional
 9                 }
10             );

      由于{page}参数并非网址路由参数之一,因此,新建的{page}参数就被替换成了QueryString参数,输出结果如下。

            /Home/About?page=1

      再举一个复杂的例子。第一条路由规则定义如下:

 1           routes.MapRoute(
 2                 "Member",                       // 路由名称
 3                 "Member/{action}/{page}",       // 带有参数的 URL
 4                 new                             // 参数默认值 
 5                 {
 6                     controller = "MemberCenter",
 7                     action = "List"
 8                 },
 9                 new
10                 {
11                     action = @"Index|List|Detail",
12                     page = @"\d+"
13                 }
14             );
15             routes.MapRoute(
16                 "Default",                     // 路由名称
17                 "{controller}/{action}/{id}",  // 带有参数的 URL
18                 new
19                 {                              // 参数默认值 
20                     controller = "Home",
21                     action = "Index",
22                     id = UrlParameter.Optional
23                 }
24             );

     在"/Views/Home/About.aspx'页面中添加以下程序代码。

 1         <%=
 2             RouteTable.Routes.GetVirtualPath(
 3                   Request.RequestContext,
 4                           new RouteValueDictionary(new { 
 5                              controller ="MemberCenter",
 6                              action="Detail"
 7                           })
 8             ).VirtualPath
 9             
10         %>

      在第2个参数中将"controller"替换成"MemberCenter",将"action"替换成"Detail",所以在获得网址之前,会先合并出一组新的路由值,如表:

字段

controller

MemberCenter

action

Detail

id

UrlParameter.Optional

     MVC会用这组新的路由值由上而下一一比对路由表中的所有路由规则,以得到最适合的路由规则。当此对比到第一个规则时,由于已经定义了两个路由参数,分别是"{action}"和"{page}",而我们的路由表中只有"action"没有"page",此时就会查看参数默认值中是否有默认的{page}参数,结果还是没有,因此比对失败。MVC并不会以这个网址路由来产生网址,进而跳至下一条网址路由进行比对。最后,比对的结果为"/MemberCenter/Detail"。

     修改"/Views/Home/About.aspx'页面的程序代码,增加"page"参数,示例如下:

 1        <%=
 2             RouteTable.Routes.GetVirtualPath(
 3                   Request.RequestContext,
 4                           new RouteValueDictionary(new { 
 5                              controller ="MemberCenter",
 6                              action = "Detail",
 7                              page = "TEST"
 8                           })
 9             ).VirtualPath
10             
11         %>

     执行以上操作后就会合并出一组新的路由值(RouteValue),如表:

字段

controller

MemberCenter

action

Detail

id

UrlParameter.Optional

page

TEST

     当我们比对第一条规则时,由于已经定义了两个路由参数,分别是{action}和{page},而我们的路由表中已有"action"和"page"的路由值了,必要参数已经全部符合,所以会进一步比对限制条件是否符合。

     由于已经定义了{page}参数的限制条件为“@"\d+"”,但路由值中的page值却是"TEST",因限制条件无法通过,所以比对还是失败了。因此,MVC并不会用这个网址路由来产生网址,进而跳至下一个网址路由进行比对。

     最后比对结果为"/MemberCenter/Detail?page=TEST"。

     总结:使用RouteTable.Routes.GetVirtualPath命名空间来生成网址大致会用到以下判断依据。

  • 若将第1个参数带入Request.RequestContext命名空间,会预先取得当前所有的路由值。也可以输入"null"代表没有默认的路由值。
  • 用当前合并后的所有路由值与网址路由表一一比对所有规则时,会先比对所有必要参数。若比对成功,就会进一步检查限制条件是否符合。
  • 若找不到必要参数,就会查找参数默认值;如果仍然找不到,则比对失败。
  • 若上述比对全部成功,RouteTable.Routes.GetVirtualPath命名空间就会用该网址路由定义的网址来生成网址。

     RouteTable.Routes.GetVirtualPath命名空间生成网址的完整流程图。

四、MVC执行的生命周期

      MVC的执行生命周期大致上分成3阶段,分别是:

     (1)网址路由比对;

     (2)执行Controller与Action;

     (3)执行View并返回结果。

      4.1 网址路由比对

      当IIS收到HTTP请求后,会先通过UrlRoutingModule处理所有与网址路由有关的运算。默认下若网址可以对应到网址根目录下的实体文件,就不会通过MVC进行处理,而是直接交由IIS或ASP.NET执行。

      若网址是"http://localhostContent/Site.css",由于在网站根目录下有"Content"目录,而且"Content"目录中也有Site.css文件,所有MVC不会将此网址解析成Content控制器和Site.css动作。

      再举一个.NET Web Forms的例子,网址为"http://localhost/Member/Login.aspx",在Global.asax文件中新建一个特殊的网址路由,具体如下。

 1           routes.MapRoute(
 2                 "Default_aspx",                     // 路由名称
 3                 "{controller}/{action.aspx}/{id}",  // 带有参数的 URL
 4                 new
 5                 {                                   // 参数默认值 
 6                     controller = "Home",
 7                     action = "Index",
 8                     id = UrlParameter.Optional
 9                 }
10             );

      在这种情况下,若在网站根目录中有使用.NET Web Forms编写的"/Member/Login.aspx"程序存在,MVC就不会应用UrlRouting,而是将流程的控制权交给IIS,并由IIS将其交友下一个模块执行。在此,就会执行"/Member/Login.aspx"程序。

      如果"/Member/Login.aspx"程序不存在,那么MVC的Routing就会正式启动比对,并且在比对到上述网址后,将执行MemberController的Login动作。

      若果在Global.asax文件的Application_Start()事件的最前面将RouteTableRoutes.RouteExistingFiles参数值设定为"true",MVC的UrlRouting就不会先判断是否有实体文件存在,程序代码如下:

1        protected void Application_Start()
2         {
3             RouteTable.Routes.RouteExistingFiles = true;
4             AreaRegistration.RegisterAllAreas();
5 
6             RegisterRoutes(RouteTable.Routes);
7         }

    设定完后,该网站收到的所以HTTP请求都会使用RegisterRoutes()方法中定义的网址路由规则一一进行比对。若比对成功,会用MVC进行处理;否则,将执行的权利交还给IIS。

NOTE : 若在MVC中是由IgnoreRoute()辅助方法比对成功的,会导致程序直接跳离MVC执行生命周期,将程序继续执行的权利交还给IIS,由IIS觉得接下来该由哪个模块(Model)或哪个处理历程(Handler)来执行。

     在使用RegisterRoutes()方法定义的网址路由规则进行比对时,事实上,比对成功后,默认值会由MvcRouteHandler决定要将改HTTP请求发送给哪个HttpHandler来执行,如图所示。MVC默认会将HTTP请求交给MvcHandler来执行。

    由图所示的MvcRouteHandler程序代码可知,若要自定义RouteHandler,只要自行开发IRouteHandler接口的类,即可通过自定义的RouteHandler来决定通过网址路由比对的网址应该交给哪个HttpHandker来执行。所以,可以通过自定义RouteHandler和HttpHandler来为MVC网站提供辅助功能。

    4.2 执行Controller和Action 4.3 执行View并返回结果

     当程序执行到MvcHandler时,HttpHandler的入口是ProcessRequest()方法。具体这里就不分析了,见书本4.4.2和4.4.3。

 

posted on 2012-11-23 13:29  Eleanore Lee  阅读(1102)  评论(1编辑  收藏  举报