今天开始学习ASP.NET MVC,在看《ASP.NET MVC架构与实战》时,看到有这样一个组件 RouteMonitor.dll,觉得挺实用的,可以用来检测Url路径的映射匹配情况,只要在浏览器中输入请求地址,就可以得到匹配的情况,并且以一种友好的页面展现给我们,如下图所示:


图一

于是乎,决定先自己分析一下该原理。

1. 我们都知道一个应用程序启动是从Application_Start事件开始的,在创建一个新的ASP.NET MVC应用程序的时候,默认会在该事件中添加

RegisterRoutes(RouteTable.Routes);

接着RegisterRoutes方法里面编写一些路由映射的方法,将请求的URL映射到相应的控制器中。

2. 现在将Application_Start事件中改写成这样的代码:

1  protected   void  Application_Start()
2  {
3      RegisterRoutes(RouteTable.Routes);
4      RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes); 
5  }

 

注意到第3行的代码,引用的就是RouteMonitor.dll的组件,通过RouteDebugger的静态方法RewriteRoutesForTesting,并且赋予一个RouteCollection的路由集合的参数,将页面映射到图一的页面,便于查看路由映射的情况。

3. 现在开始分析下RouteMonitor.dll里面都有些什么呢。

里面包含DebugHttpHandler类,DebugRoute类,DebugRouteHandler类,RouteDebugger类,我们先看看RouteDebugger类都做了些什么:
 1  public   static   class  RouteDebugger
 2  {
 3       public   static   void  RewriteRoutesForTesting(RouteCollection routes)
 4      {
 5           // 可对路由集合类进行多线程同步访问
 6           using  (routes.GetReadLock())
 7          {
 8               bool  flag  =   false ;
 9               foreach  (RouteBase base2  in  routes)
10              {
11                  Route route  =  base2  as  Route;
12                   if  (route  !=   null )
13                  {
14                      route.RouteHandler  =   new  DebugRouteHandler();
15                  }
16                   if  (route  ==  DebugRoute.Singleton)
17                  {
18                      flag  =   true ;
19                  }
20              }
21               if  ( ! flag)
22              {
23                  routes.Add(DebugRoute.Singleton);
24              }
25          }
26      }
27  }


其中routes.GetReadLock()可对路由集合类进行多线程同步访问:

1  public  IDisposable GetReadLock()
2  {
3       this ._rwLock.AcquireReaderLock( - 1 );
4       return   new  ReadLockDisposable( this ._rwLock);
5  }

AcquireReaderLock方法去请求得到该读写锁,并且设置超时时间为Infinite(无限),当using方法域结束后将释放该读写锁。

(另:ReaderWriterLock 用于同步对资源的访问。在任一特定时刻,它允许多个线程同时进行读访问,或者允许单个线程进行写访问。在资源不经常发生更改的情况下,ReaderWriterLock 所提供的吞吐量比简单的一次只允许一个线程的锁(如 Monitor)更高。)

接着从routes遍历所有的Route类,在这里我们改变它的路由处理程序,该路由处理程序类的接口为IRouteHandler,用RouteMonitor自带的DebugRouteHandler去替换它原有的RouteHandler,以便后面改变Http处理程序的“方向”。

我们先接着看后面的代码,这里有个routes.Add(DebugRoute.Singleton),DubugRoute继承于Route类,它的构造函数实现于构造可捕获所有URL地址的Route。DebugRoute.Singleton作为单一实例,代码如下:

 1  public   class  DebugRoute : Route
 2  {
 3       private   static  DebugRoute singleton  =   new  DebugRoute(); 
 4 
 5       // 可捕获所有的URL地址的Route
 6       private  DebugRoute()
 7          :  base ( " {*catchall} " new  DebugRouteHandler())
 8      {
 9      } 
10 
11       public   static  DebugRoute Singleton
12      {
13           get
14          {
15               return  singleton;
16          }
17      }
18  }

 


4. 接着分析DebugRouteHandler的路由处理程序:
1  public   class  DebugRouteHandler : IRouteHandler
2  {
3       public  IHttpHandler GetHttpHandler(RequestContext requestContext)
4      {
5          DebugHttpHandler handler  =   new  DebugHttpHandler();
6          handler.RequestContext  =  requestContext;
7           return  handler;
8      }
9  }

这样它可以获得实现IHttpHanlder接口的DebugHttpHandler类的实例化对象,这个实例化对象中也传入了一个RequestContext 对象实例。


5. 最后看下这个以IHttpHanlder为接口的DebugHttpHandler类,用于进行Http处理的程序:

 1  public   void  Proce***equest(HttpContext context)
 2  {
 3       string  format  =   " <html>\r\n<head>\r\n    <title>路由监测</title>\r\n    <style>\r\n        body, td, th {{font-family: verdana; font-size: .8em;}}\r\n        caption {{font-weight: bold;}}\r\n        tr.header {{background-color: #ffc;}}\r\n        label {{font-weight: bold; }}\r\n        .false {{color: #c00;}}\r\n        .true {{color: #0c0;}}\r\n    </style>\r\n</head>\r\n<body>\r\n<div id=\ " main\ " >\r\n    <p class=\ " message\ " >\r\n        在浏览器中键入请求地址,可以监测匹配的路由。\r\n    </p>\r\n    <p><label>Route</label>: {1}</p>\r\n    <div style=\ " float : left;\ " >\r\n        <table border=\ " 1 \ "  cellpadding=\ " 3 \ "  cellspacing=\ " 0 \ "  width=\ " 300 \ " >\r\n            <caption>Route Data</caption>\r\n            <tr class=\ " header\ " ><th>Key</th><th>Value</th></tr>\r\n            {0}\r\n        </table>\r\n    </div>\r\n    <div style=\ " float : left; margin - left: 10px;\ " >\r\n        <table border=\ " 1 \ "  cellpadding=\ " 3 \ "  cellspacing=\ " 0 \ "  width=\ " 300 \ " >\r\n            <caption>Data Tokens</caption>\r\n            <tr class=\ " header\ " ><th>Key</th><th>Value</th></tr>\r\n            {4}\r\n        </table>\r\n    </div>\r\n    <hr style=\ " clear: both;\ "  />\r\n    <table border=\ " 1 \ "  cellpadding=\ " 3 \ "  cellspacing=\ " 0 \ " >\r\n        <caption>All Routes</caption>\r\n        <tr class=\ " header\ " >\r\n            <th>Matches Current Request</th>\r\n            <th>Url</th>\r\n            <th>Defaults</th>\r\n            <th>Constraints</th>\r\n            <th>DataTokens</th>\r\n        </tr>\r\n        {2}\r\n    </table>\r\n    <hr />\r\n    <strong>AppRelativeCurrentExecutionFilePath</strong>: {3}\r\n</div>\r\n</body>\r\n</html> " ;
 4       string  str2  =   string .Empty; 
 5 
 6       // RouteData类包含所请求路由的相关值
 7      RouteData routeData  =   this .RequestContext.RouteData; 
 8 
 9       // 获得路由的URL参数值和默认值的集合
10      RouteValueDictionary values  =  routeData.Values; 
11 
12       // 获取路由的对象
13      RouteBase base2  =  routeData.Route; 
14 
15       string  str3  =   string .Empty;
16       using  (RouteTable.Routes.GetReadLock())
17      {
18           foreach  (RouteBase base3  in  RouteTable.Routes)
19          {
20               // 返回有关集合中与指定值匹配的路由的信息,如果为空,说明不匹配
21               bool  flag  =  base3.GetRouteData( this .RequestContext.HttpContext)  !=   null ;
22               string  str4  =   string .Format( " <span class=\ " { 0 }\ " >{0}</span> " , flag);
23               string  url  =   " n/a " ;
24               string  str6  =   " n/a " ;
25               string  str7  =   " n/a " ;
26               string  str8  =   " n/a " ;
27              Route route  =  base3  as  Route; 
28 
29               // 如果路由不为空
30               if  (route  !=   null )
31              {
32                   // 得到匹配的Url路由
33                  url  =  route.Url; 
34 
35                   // 得到默认的Url匹配规则信息
36                  str6  =  FormatRouteValueDictionary(route.Defaults);
37                   // 得到约束的Url匹配规则信息
38                  str7  =  FormatRouteValueDictionary(route.Constraints);
39                   // 得到命名空间的Url匹配规则信息
40                  str8  =  FormatRouteValueDictionary(route.DataTokens);
41              }
42              str3  =  str3  +   string .Format( " <tr><td>{0}</td><td>{1}</td><td>{2}</td><td>{3}</td><td>{3}</td></tr> " new   object [] { str4, url, str6, str7, str8 });
43          }
44      }
45       string  str9  =   " n/a " ;
46       string  str10  =   ""
47 
48       // 如果只被{@cacheall}捕获时,提示不匹配
49       if  (base2  is  DebugRoute)
50      {
51          str9  =   " <strong class=\ " false \ " >NO MATCH!</strong> " ;
52      }
53       else
54      {
55           // 匹配的路由信息
56           foreach  ( string  str11  in  values.get_Keys())
57          {
58              str2  =  str2  +   string .Format( " \t<tr><td>{0}</td><td>{1}&nbsp;</td></tr> " , str11, values.get_Item(str11));
59          }
60           foreach  ( string  str11  in  routeData.DataTokens.get_Keys())
61          {
62              str10  =  str10  +   string .Format( " \t<tr><td>{0}</td><td>{1}&nbsp;</td></tr> " , str11, routeData.DataTokens.get_Item(str11));
63          }
64          Route route2  =  base2  as  Route;
65           if  (route2  !=   null )
66          {
67              str9  =  route2.Url;
68          }
69      }
70      context.Response.Write( string .Format(format,  new   object [] { str2, str9, str3, context.Request.AppRelativeCurrentExecutionFilePath, str10 }));
71 

 

通过它Proce***equest来处理请求,最后呈现路由检测的页面。

从中可以看到,首先从RequestContext.RouteData可以得到RouteData类,RouteData类包含所请求路由的相关值。

从RouteData.Values获取路由的 URL 参数值和默认值的集合。

从routeData.Route获取路由的对象。

GetRouteData的方法获取有关集合中与指定值匹配的路由的信息。

通过调用FormatRouteValueDictionary方法得到每一条路由的相关值:

 1  private   static   string  FormatRouteValueDictionary(RouteValueDictionary values)
 2   {
 3        if  (values  ==   null )
 4       {
 5            return   " (null) " ;
 6       }
 7        string  str  =   string .Empty;
 8        // 遍历路由键/值对的集合
 9        foreach  ( string  str2  in  values.get_Keys())
10       {
11           str  =  str  +   string .Format( " {0} = {1},  " , str2, values.get_Item(str2));
12       }
13        if  (str.EndsWith( " " ))
14       {
15           str  =  str.Substring( 0 , str.Length  -   2 );
16       }
17        return  str;
18   }

通过重写

context.Response.Write(string.Format(format, new object[] { str2, str9, str3, context.Request.AppRelativeCurrentExecutionFilePath, str10 }));
页面中就呈现出路由匹配的检测信息了。