好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Asp.net MVC:深入理解路由

Asp.net MVC:深入理解路由

Asp.net MVC:深入理解路由(一)

原创文章,转载请注明出处: http://HdhCmsTestcnblogs测试数据/wu-jian

前言

从.Net Framework 1.0时代开始写WebForm,直到最近断断续续看到Razor的语法风格,然后搜了Asp.net MVC的一些介绍,于是想把自己的一个项目从WebForm转换成MVC。边摸索边Coding,花了两周时间,差不多大功告成了。项目中有用到多级域名,代码写到这部分突然就被卡住了,一直没深入了解Asp.net Routing,被卡住理所当然,感觉关于Asp.net MVC底层知识的文章很少,大多是一些应用层面,或底层原理的部分片段,不能帮助我系统的理解。代码是最好的学习和交流方式,于是结合自己的项目实例,反编译了部分微软的类库来参考,将过程整理和表述,如能给人予帮助,不甚荣幸,同时个人能力有限,不足之处还请及时指正。

“静态路由表”

在Asp.net中,需要将请求的URL根据我们设置的匹配规则交给相应的处理程序去处理,从Url到处理程序的过程,称之为路由,如下图所示:

<图1>

在Asp.net中,命名空间  System.Web.Routing   包含了路由的所有功能,从命名上可以看出它与  System.Web.Mvc   是平级的。没错,Asp.net Routing是一个独立组件,你可以用它来路由MVC,路由Web Form,甚至路由自己的Web框架。

在深入Routing之前,我们暂且按最简单的方式来想像路由:在内存中有一张表,这张表记录了Url的匹配规则和处理程序,当一个Url请求到达时,遍历这张表,如发现Url与规则匹配,交由到对应的处理程序。

OK,按这个简单逻辑我们首先需要找到这张表。它位于  System.Web.Routing.RouteTable.Routes   静态属性中,类型为  RouteCollection , RouteCollection为  Route  的集合,当我们要添加一条路由信息时,即向这个集合中添加一个Route对象。为方便描述,本文中姑且我们把这个存放Route集合的静态属性称之为“静态路由表”吧。

 public   class   RouteTable
{
          public   RouteTable();

          //  静态属性获取所有路由的对象 
         public   static  RouteCollection Routes {  get  ; }
} 

向“静态路由表”中添加记录

如何向“静态路由表”中添加记录?有如下两种方式:

一、直接调用System.Web.Routing.RouteCollection.Add()方法

 public   void  Add( string   name, RouteBase item)
{
      //  ... 
}

二、调用System.Web.Mvc.RouteCollectionExtensions.MapRoute()扩展方法

扩展方法是C#3.0的新特性,比如假设微软的团队A开发了System.Web.Routing,团队B开发了System.Web.Mvc。团队B需要使用团队A开发的RouteCollection.Add(),同时还需要再对它进行一些扩展,此时团队B使用了扩展方法而无需关心团队A的代码: 

反编译  System.Web.Mvc.RouteCollectionExtensions.MapRoute  方法

 public   static  Route MapRoute( this  RouteCollection routes,  string  name,  string  url,  object  defaults,  object  constraints,  string  [] namespaces)
{
      if  (routes ==  null  )
    {
          throw   new  ArgumentNullException( "  routes  "  );
    }
      if  (url ==  null  )
    {
          throw   new  ArgumentNullException( "  url  "  );
    }
    Route item  =  new  Route(url,  new   MvcRouteHandler()) {
        Defaults  =  new   RouteValueDictionary(defaults),
        Constraints  =  new   RouteValueDictionary(constraints),
         DataTokens  =  new   RouteValueDictionary()
    };
      if  ((namespaces !=  null ) && (namespaces.Length >  0  ))
    {
        item.DataTokens[  "  Namespaces  " ] =  namespaces;
    }
    routes.Add(name, item);
      return   item;
} 

对扩展方法总结:

1、扩展方法与方法所在的类都必须为静态的。

2、第1个参数为待扩展的类型,并且前面添加this关键字。

“静态路由表”的数据结构

System.Web.Routing.Route 类

 public   class   Route : RouteBase
{
      ///   <summary> 
     ///   使用指定的 URL 模式和处理程序类初始化 System.Web.Routing.Route 类的新实例
      ///   </summary> 
     ///   <param name="url">  路由的 URL 模式  </param> 
     ///   <param name="routeHandler">  处理路由请求的对象  </param> 
     public  Route( string   url, IRouteHandler routeHandler);

      ///   <summary> 
     ///   使用指定的 URL 模式、默认参数值和处理程序类初始化 System.Web.Routing.Route 类的新实例
      ///   </summary> 
     ///   <param name="url">  路由的 URL 模式  </param> 
     ///   <param name="defaults">  用于 URL 中缺失的任何参数的值  </param> 
     ///   <param name="routeHandler">  处理路由请求的对象  </param> 
     public  Route( string   url, RouteValueDictionary defaults, IRouteHandler routeHandler);

      ///   <summary> 
     ///   使用指定的 URL 模式、默认参数值、约束和处理程序类初始化 System.Web.Routing.Route 类的新实例
      ///   </summary> 
     ///   <param name="url">  路由的 URL 模式  </param> 
     ///   <param name="defaults">  要在 URL 不包含所有参数时使用的值  </param> 
     ///   <param name="constraints">  一个用于指定 URL 参数的有效值的正则表达式  </param> 
     ///   <param name="routeHandler">  处理路由请求的对象  </param> 
     public  Route( string   url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler);

      ///   <summary> 
     ///   使用指定的 URL 模式、默认参数值、约束、自定义值和处理程序类初始化 System.Web.Routing.Route 类的新实例
      ///   </summary> 
     ///   <param name="url">  路由的 URL 模式  </param> 
     ///   <param name="defaults">  要在 URL 不包含所有参数时使用的值  </param> 
     ///   <param name="constraints">  一个用于指定 URL 参数的有效值的正则表达式  </param> 
     ///   <param name="dataTokens">  传递到路由处理程序但未用于确定该路由是否匹配特定 URL 模式的自定义值。 这些值会传递到路由处理程序,以便用于处理请求  </param> 
     ///   <param name="routeHandler">  处理路由请求的对象  </param> 
     public  Route( string   url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler);

      ///   <summary> 
     ///   获取或设置为 URL 参数指定有效值的表达式的词典
      ///   返回一个包含参数名称和表达式的对象
      ///   </summary> 
     public  RouteValueDictionary Constraints {  get ;  set  ; }

      ///   <summary> 
     ///   获取或设置传递到路由处理程序但未用于确定该路由是否匹配 URL 模式的自定义值
      ///   返回一个包含自定义值的对象
      ///   </summary> 
     public  RouteValueDictionary DataTokens {  get ;  set  ; }

      ///   <summary> 
     ///   获取或设置要在 URL 不包含所有参数时使用的值
      ///   返回一个包含参数名称和默认值的对象
      ///   </summary> 
     public  RouteValueDictionary Defaults {  get ;  set  ; }

      ///   <summary> 
     ///   获取或设置处理路由请求的对象
      ///   返回处理请求的对象
      ///   </summary> 
     public  IRouteHandler RouteHandler {  get ;  set  ; }

      ///   <summary> 
     ///   获取或设置路由的 URL 模式
      ///   返回用于匹配路由和 URL 的模式
      ///   </summary> 
     public   string  Url {  get ;  set  ; }

      ///   <summary> 
     ///   返回有关所请求路由的信息
      ///   </summary> 
     ///   <param name="httpContext">  一个对象,封装有关 HTTP 请求的信息  </param> 
     ///   <returns>  返回一个对象,其中包含路由定义中的值  </returns> 
     public   override   RouteData GetRouteData(HttpContextBase httpContext);

      ///   <summary> 
     ///   返回与路由关联的 URL 的相关信息
      ///   </summary> 
     ///   <param name="requestContext">  一个对象,封装有关所请求的路由的信息  </param> 
     ///   <param name="values">  一个包含路由参数的对象  </param> 
     ///   <returns>  返回一个包含与路由关联的 URL 的相关信息的对象  </returns> 
     public   override   VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);

      ///   <summary> 
     ///   确定参数值是否与该参数的约束匹配
      ///   </summary> 
     ///   <param name="httpContext">  一个对象,封装有关 HTTP 请求的信息  </param> 
     ///   <param name="constraint">  用于测试 parameterName 的正则表达式或对象  </param> 
     ///   <param name="parameterName">  要测试的参数的名称  </param> 
     ///   <param name="values">  要测试的值  </param> 
     ///   <param name="routeDirection">  一个指定 URL 路由是否处理传入请求或构造 URL 的值  </param> 
     ///   <returns>  如果参数值与约束匹配,则为 true;否则为 false。  </returns> 
     protected   virtual   bool  ProcessConstraint(HttpContextBase httpContext,  object  constraint,  string   parameterName, RouteValueDictionary values, RouteDirection routeDirection);
} 

静态路由表的数据结构即为Route类的结构,它遵循一定规则存放数据,如上代码所示,将设置的Url规则存放于Url属性中,将处理程序存放于RouteHandler属性中。

将用户设置的其它数据存放于字典(RouteValueDictionary)中,并将字典分为3个类别:

Defaults:描述Url规则的默认值

Constraints:描述Url规则的约束

DataTokens:其它自定义数据(如处理程序的命名空间)

为结合项目应用更直观的演示静态路由表的数据结构,我编写了一个简单的DEMO

向“静态路由表”中添加数据:

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

        routes.MapRoute(
            name:   "  Default  "  ,
            url:   "  {controller}/{action}/{id}  "  ,
            defaults:   new  { controller =  "  Home  " , action =  "  Index  " , id =  UrlParameter.Optional },
            constraints:   new  { controller =  "  [a-zA-Z]+  "   },
            namespaces:   new   string [] {  "  WebRoutingDemo.Controllers.HomeController  "   }                
        );
    }
} 

打印“静态路由表”中所有数据:

 protected   void  Page_Load( object   sender, EventArgs e)
{
      //  静态路由表 
    System.Web.Routing.RouteCollection routeItems =  System.Web.Routing.RouteTable.Routes;

      //  遍历静态路由表 
     foreach  (System.Web.Routing.Route routeItem  in   routeItems)
    {
          //  Url 
        Response.Write( "  <strong>Url:</strong><br />  "  );
        Response.Write(  string .Format( "  &nbsp;&nbsp;&nbsp;&nbsp;{0}<br />  "  , routeItem.Url));

          //  Defaults 
        Response.Write( "  <strong>Defaults:</strong><br />  "  );
          if  (routeItem.Defaults !=  null  )
        {
              foreach  (KeyValuePair< string ,  object > defaultItem  in   routeItem.Defaults)
            {
                Response.Write(  string .Format( "  &nbsp;&nbsp;&nbsp;&nbsp;{0}:{1}<br />  "  , defaultItem.Key, defaultItem.Value));
            }
        }

          //  Constraints 
        Response.Write( "  <strong>Constraints:</strong><br />  "  );
          if  (routeItem.Constraints !=  null  )
        {
              foreach  (KeyValuePair< string ,  object > constraintsItem  in   routeItem.Constraints)
            {
                Response.Write(  string .Format( "  &nbsp;&nbsp;&nbsp;&nbsp;{0}:{1}<br />  "  , constraintsItem.Key, constraintsItem.Value));
            }
        }

          //  DataTokens  
        Response.Write( "  <strong>DataTokens:</strong><br />  "  );
          if  (routeItem.DataTokens !=  null  )
        {
              foreach  (KeyValuePair< string ,  object > dataTokensItem  in   routeItem.DataTokens)
            {
                Response.Write(  string .Format( "  &nbsp;&nbsp;&nbsp;&nbsp;{0}:{1}<br />  "  , dataTokensItem.Key, dataTokensItem.Value));
            }
        }

        Response.Write(  "  =============================<br />  "  );
    }
} 

打印结果:

Url:
    api/{controller}/{id}
Defaults:
    id:
Constraints:
DataTokens:
=============================
Url:
    {resource}.axd/{*pathInfo}
Defaults:
Constraints:
DataTokens:
=============================
Url:
    {controller}/{action}/{id}
Defaults:
    controller:Home
    action:Index
    id:
Constraints:
    controller:[a-zA-Z]+
DataTokens:
    Namespaces:System.String[]
=============================
 

路由的核心逻辑

路由的核心逻辑是将请求的URL与“静态路由表”中配置的URL规则进行比较,找到第一条匹配的记录,并将请求交由其处理程序(RouteHandler)进行后续处理。

请求的URL如何与“静态路由表”进行匹配?我猜想过程应该是这样:首先一个URL请求到达,然后遍历“静态路由表”,将表中的URL表达式与请求的URL进行比较,如请求URL符合该表达式,获取RouteHandler并中止遍历。当然这只是我的猜想,实际微软把这个过程封装在了System.Web.Routing.ParsedRoute类中,感兴趣的朋友可通过反编译查看详细过程。

在应用层面,微软为开发者提供了一个扩展点以实现对路由的自定义处理:System.Web.Routing.RouteBase.GetRouteData()方法。这是一个抽象方法,也就是说只要是“静态路由表”中的记录,都必须实现这个方法。如下代码:

System.Web.Routing.RouteBase 类

 public   abstract   class   RouteBase
{
      protected   RouteBase();

      ///   <summary> 
     ///   当在派生类中重写时,会返回有关请求的路由信息
      ///   </summary> 
     ///   <param name="httpContext">  一个对象,封装有关 HTTP 请求的信息  </param> 
     ///   <returns>  一个对象,包含路由定义的值(如果该路由与当前请求匹配)或 null(如果该路由与请求不匹配)  </returns> 
     public   abstract   RouteData GetRouteData(HttpContextBase httpContext);

      ///   <summary> 
     ///   当在派生类中重写时,会检查路由是否与指定值匹配,如果匹配,则生成一个 URL,然后检索有关该路由的信息
      ///   </summary> 
     ///   <param name="requestContext">  一个对象,封装有关所请求的路由的信息  </param> 
     ///   <param name="values">  一个包含路由参数的对象  </param> 
     ///   <returns>  一个对象(包含生成的 URL 和有关路由的信息)或 null(如果路由与 values 不匹配)  </returns> 
     public   abstract   VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
} 

通过GetRouteData()方法判断请求的URL是否与“静态路由表”中的记录匹配,如返回一个RouteData对象,表示匹配成功,循环中止;如返回null,表示匹配失败,循环继续,直到遍历完整个“静态路由表”。大致逻辑如下图所示:

<图2>

System.Web.Routing.RouteData类

 public   class   RouteData
{
      public   RouteData();
      public   RouteData(RouteBase route, IRouteHandler routeHandler);

      ///   <summary> 
     ///   获取在 ASP.NET 路由确定路由是否匹配请求时,传递到路由处理程序但未使用的自定义值的集合。
      ///   属性Namespaces表示辅助Controller类型的解析而设置的命名空间列表,该属性值从DataTokens字典中提取,对应的Key为namespaces
      ///   返回一个包含自定义值的对象。
      ///   </summary> 
     public  RouteValueDictionary DataTokens {  get  ; }

      ///   <summary> 
     ///   获取或设置表示路由的对象。
      ///   返回一个表示路由定义的对象。
      ///   </summary> 
     public  RouteBase Route {  get ;  set  ; }

      ///   <summary> 
     ///   获取或设置处理所请求路由的对象。
      ///   返回一个处理路由请求的对象。
      ///   </summary> 
     public  IRouteHandler RouteHandler {  get ;  set  ; }

      ///   <summary> 
     ///   获取路由的 URL 参数值和默认值的集合。
      ///   直接从请求地址解析出来的变量
      ///   Controller和Action名称的同名属性直接从Values字典中提取,对应的Key分别为controller和action
      ///   返回一个对象,其中包含根据 URL 和默认值分析得出的值。
      ///   </summary> 
     public  RouteValueDictionary Values {  get  ; }

      ///   <summary> 
     ///   使用指定标识符检索值。
      ///   </summary> 
     ///   <param name="valueName">  要检索的值的键。  </param> 
     ///   <returns>  其键与 valueName 匹配的 System.Web.Routing.RouteData.Values 属性中的元素。  </returns> 
     public   string  GetRequiredString( string   valueName);
} 

总结

我们设置的URL规则通过System.Web.Routing.RouteCollection.Add()方法添加进“静态路由表”,每条规则以一个Route对象存放,对象的Defaults属性存放了我们设置的URL默认值,Constraints属性存放了对URL的一些约束,DataTokens属性存放其它数据。

当用户浏览网页时,请求的URL与静态路由表的记录进行匹配,匹配逻辑和过程包括在实现了RouteBase.GetRouteData的一个方法中,微软的Route.GetRouteData为我们提供了一个默认实现,当然我们也可以在自定义的方法中实现,如DEMO代码所示。

URL与“静态路由表”是否匹配取决于GetRouteData方法是否返回RouteData对象,如果RouteData为null表示未匹配。如果RouteData不为null,则表示URL与“静态路由表”相匹配,并将RouteHandler的值传递到RouteData中,供后续逻辑使用。

DEMO

DEMO参考了DomainRoute的源代码,示例了一个自定义的路由处理过程,它实现了GetRouteData方法,演示了路由处理的一些细节,包括URL规则与请求URL的匹配过程、将URL中的名称参数、Defaults、DataTokens还原为RouteData对象。

下载地址: https://files.cnblogs测试数据/wu-jian/WebRoutingDemo.rar  

 

分类:  Asp.net MVC

标签:  asp.net mvc ,  mvc routing ,  mvc route ,  mvc路由 ,  getroutedata

利用URLRewriter重写实现伪二级域名

最近公司有个项目,要求将请求的参数放到放置到网址的前面

  例:原请求网址: HdhCmsTestabc测试数据?jc=dfs  改写层 dfs.abc测试数据,实现这种伪的二级域名。着实下了一番功夫,园里有不少大大已经写过同样的文章了。今天我就在这总结下。

第一步:下载一个URLRewriter。

  微软官方 关于URLRewriter的解释

   http://HdhCmsTestmicrosoft测试数据/china/msdn/library/webservices/asp.net/URLRewriting.mspx

  进入以后可以下载源代码,当然也可以去别的地方下载别人修改过的代码。要安装,安装完毕以后,在“我的文档”→“MSDN”→“URL Rewriting in ASP.NET” →“URLRewritingCode.sln”     

第二步: 要对URLRewriter里的方法重写

  这里我们要重写的,就是URLRewriter程序集下的两个文件 “BaseModuleRewriter.cs”和“ModuleRewriter.cs”

  1.BaseModuleRewriter.cs

   BaseModuleRewriter_AuthorizeRequest原方法

  改为

   BaseModuleRewriter_AuthorizeRequest修改过代码

  2.ModuleRewriter.cs

   Rewrite方法中原for循环

  改为

   Rewrite方法中修改后for循环

  修改完成以后,重新生成,在bin文件下找到Debug文件夹中找到URLRewriter.dll这个文件。

  在自己的项目中引用这个DLL文件

第三步:在自己的项目的web.config文件中坐下配置,就可以了

  1.在<configSections>节点下添加

    <section name="RewriterConfig" type="URLRewriter.Config.RewriterConfigSerializerSectionHandler, URLRewriter" />

  2.在<configuration>节点(即根目录的节点下)

    <RewriterConfig>
        <Rules>
          <RewriterRule>
            <LookFor>http://(\w+).abc测试数据/</LookFor>
            <SendTo>/Index.aspx?jv=$1</SendTo>
          </RewriterRule>
        </Rules>
      </RewriterConfig>

    如果有多个匹配地址可以写多个,<Rules>节点中多写几个<RewriterRule> 就可以了!
  3.在<system.web>节点下<httpModules>节点中添加

    <add type="URLRewriter.ModuleRewriter, URLRewriter" name="ModuleRewriter" />

  这样,基本就配置完毕了!

第四部:如果要自己测试,要把程序放到IIS服务器上,进行测试。要自己修改下hosts文件,写个虚假的域名指向程序!

 如果有需要Demo的,请留言!园子不让上传文件!抱歉

虽然这样很方便,但是就在我写文章的同时,看到一篇关于URL重写的园子里的一篇大大写的文章

链接帖出来

http://HdhCmsTestcnblogs测试数据/csky/archive/2006/08/09/urlrewrite.html

第二次写博客文章,虽然经常来园子,但是经常是伸手党,现在自己写文章,才知道不好写啊!

欢迎大家多多和我交流!   

 

 

标签:  URLRewriter重写 ,  二级域名

作者: Leo_wl

    

出处: http://HdhCmsTestcnblogs测试数据/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于Asp.net MVC:深入理解路由的详细内容...

  阅读:69次