好得很程序员自学网

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

ASP.NET路由系统实现原理:HttpHandler的动态映射

ASP.NET路由系统实现原理:HttpHandler的动态映射

我们知道一个请求最终通过一个具体的HttpHandler进行处理,而我们熟悉的用于表示一个Web页面的Page对象就是一个HttpHandler,被用于处理基于某个.aspx文件的请求。我们可以通过HttpHandler的动态映射来实现请求地址与物理文件路径之间的分离。实际上ASP.NET路由系统就是采用了这样的实现原理。如下图所示,ASP.NET路由系统通过一个注册到当前应用的自定义HttpModule对所有的请求进行拦截,并通过对请求的分析为之动态匹配一个用于处理它的HttpHandler。HttpHandler对请求进行处理后将相应的结果写入HTTP回复以实现对请求的相应。

目录 
一、UrlRoutingModule 
一、UrlRoutingModule 
二、PageRouteHandler V.S. MvcRouteHandler 
三、ASP.NET路由系统扩展 
        实例演示:通过自定义Route对ASP.NET路由系统进行扩展

上图所示的作为请求拦截器的HttpModule类型为 UrlRoutingModule 。如下面的代码片断所示,UrlRoutingModule对请求的拦截是通过注册表示当前应用的HttpApplication的PostResolveRequestCache事件实现的。

    1:   public   class  UrlRoutingModule : IHttpModule
    2:  {
    3:       //其他成员 
    4:       public  RouteCollection RouteCollection { get; set; }
    5:       public   void  Init(HttpApplication context)
    6:      {
    7:          context.PostResolveRequestCache +=  new  EventHandler( this .OnApplicationPostResolveRequestCache);
    8:   
    9:      }
   10:       private   void  OnApplicationPostResolveRequestCache( object  sender,  EventArgs e);
   11:  }

UrlRoutingModule具有一个类型为RouteCollection的RouteCollection属性,在默认的情况下引用这 通过RouteTable的静态属性Routes表示的全局路由表 。针对请求的HttpHandler的动态映射就实现在OnApplicationPostResolveRequestCache方法中,具体的实现逻辑非常简单:通过HttpApplication获得但前的HTTP上下文,并将其作为参数调用RouteCollection的GetRouteData方法得到一个RouteData对象。

通过RouteData的RouteHandler属性可以得到一个实现了IRouteHandler的路由处理器对象,而调用后者的GetHttpHandler方法直接可以获取对应的HttpHandler对象,而我们需要映射到当前请求的就是这么一个 HttpHandler。下面的代码片断基本上体现了定义在UrlRoutingModule的OnApplicationPostResolveRequestCache方法中的动态HttpHandler映射逻辑。

    1:   public   class  UrlRoutingModule : IHttpModule
    2:  {
    3:       //其他成员     
    4:       private   void  OnApplicationPostResolveRequestCache( object  sender, EventArgs e)
    5:      { 
    6:          HttpContext context = ((HttpApplication)sender).Context;
    7:          HttpContextBase contextWrapper =  new  HttpContextWrapper(context);
    8:          RouteData routeData =  this .RouteCollection.GetRouteData(contextWrapper);
    9:          RequestContext requestContext =  new  RequestContext(contextWrapper, routeData);
   10:          IHttpHandler handler = routeData.RouteHandler.GetHttpHandler(requestContext);
   11:          context.RemapHandler(handler);
   12:      }
   13:  }

二、 PageRouteHandler V.S. MvcRouteHandler

通过前面的介绍我们知道对于调用RouteCollection的GetRouteData获得的RouteData对象,其RouteHandler来源于匹配的Route对象。对于通过调用RouteCollection的MapPageRoute方法注册的Route来说,它的RouteHandler是一个类型为 PageRouteHandler 对象。

由于调用MapPageRoute方法的目的在于实现请求地址与某个.aspx页面文件之间的映射,所以我们最终还是要创建的Page对象还处理相应的请求,所以PageRouteHandler的GetHttpHandler方法最终返回的就是针对映射页面文件路径的Page对象。此外,MapPageRoute方法中还可以控制是否对物理文件地址实施授权,而授权在返回Page对象之前进行。

定义在PageRouteHandler中的HttpHandler获取逻辑基本上体现在如下的代码片断中,两个属性VirtualPath和CheckPhysicalUrlAccess表示页面文件的地址和是否需要对物理文件地址实施URL授权,它们在构造函数中被初始化,而最终来源于调用RouteCollection的MapPageRoute方法传入的参数。

    1:   public   class  PageRouteHandler : IRouteHandler
    2:  {
    3:       public   bool  CheckPhysicalUrlAccess { get;  private  set; }
    4:       public   string  VirtualPath { get;  private  set; }
    5:       public  PageRouteHandler( string  virtualPath,  bool  checkPhysicalUrlAccess)
    6:      {
    7:           this .VirtualPath = virtualPath;
    8:           this .CheckPhysicalUrlAccess = checkPhysicalUrlAccess;
    9:      }
   10:       public  IHttpHandler GetHttpHandler(RequestContext requestContext)
   11:      {
   12:           if  ( this .CheckPhysicalUrlAccess)
   13:          {
   14:               //Check Physical Url Access 
   15:          }
   16:           return  (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath( this .VirtualPath,  typeof (Page))
   17:      }
   18:  }

ASP.NET MVC的Route对象是通过调用RouteCollection的扩展方法MapRoute方法进行注册的,它对应的RouteHandler是一个类型为 MvcRouteHandler 的对象。如下面的代码片断所示,MvcRouteHandler用于获取处理当前请求的HttpHandler是一个 MvcHandler 对象。MvcHandler实现对Controller的激活、Action方法的执行以及对请求的相应,毫不夸张地说,整个MVC框架实现在MvcHandler之中。

    1:   public   class  MvcRouteHandler : IRouteHandler
    2:  {    
    3:       //其他成员 
    4:       public  IHttpHandler GetHttpHandler(RequestContext requestContext)
    5:      {
    6:           return   new  MvcHandler(requestContext)
    7:      }
    8:  }

三、 ASP.NET路由系统扩展

到此为止我们已经对ASP.NET的路由系统的实现进行了详细介绍,总的来说,整个路由系统是通过对HttpHandler的动态注册的方式来实现的。具体来说,UrlRoutingModule通过对代表Web应用的HttpApplication的PostResolveRequestCache事件的注册实现了对请求的拦截。对于被拦截的请求,UrlRoutingModule利用注册的路由表对其进行匹配和解析,进而得到一个包含所有路由信息的RouteData对象。最终借助该对象的RouteHandler创建出相应的HttpHandler映射到当前请求。从可扩展性的角度来讲,我们可以通过如下三种方式来实现我们需要的路由方式。

通过集成抽象类RouteBase创建自定义Route定制路由逻辑。 通过实现接口IRouteHandler创建自定义RouteHandler定制HttpHandler提供机制。 通过实现IHttpHandler创建自定义HttpHandler来对请求处理。

实例演示:通过自定义Route对ASP.NET路由系统进行扩展

定义在ASP.NET路由系统中默认的路由类型Route建立了定义成文本模板的URL模式与某个物理文件之间的映射,如果我们对WCF REST有一定的了解,应该知道其中也有类似的实现。具体来说,WCF REST借助于System.UriTemplate这个对象实现了同样定义成某个文本模板的URI模式与目标操作之间的映射。篇幅所限,我们不能对WCF REST的UriTemplate作详细的介绍,有兴趣的读者可以参考《 UriTemplate、UriTemplateTable与WebHttpDispatchOperationSelector 》。[源代码从 这里 下载]

我们创建一个新的ASP.NET Web应用,并且添加针对程序集System.ServiceModel.dll的引用(UriTemplate定义在该程序集中),然后创建如下一个针对UriTemplate的路由类型UriTemplateRoute。

    1:   public   class  UriTemplateRoute:RouteBase
    2:  {
    3:       public  UriTemplate   UriTemplate { get;  private  set; }
    4:       public  IRouteHandler     RouteHandler { get;  private  set; }
    5:       public  RouteValueDictionary     DataTokens { get;  private  set; }
    6:   
    7:       public  UriTemplateRoute( string  template,  string  physicalPath,  object  dataTokens =  null )
    8:      {
    9:           this .UriTemplate =  new  UriTemplate(template);
   10:           this .RouteHandler =  new  PageRouteHandler(physicalPath);
   11:           if  ( null  != dataTokens)
   12:          {
   13:               this .DataTokens =  new  RouteValueDictionary(dataTokens);
   14:          }
   15:           else 
   16:          {
   17:               this .DataTokens =  new  RouteValueDictionary();
   18:          }
   19:      }
   20:       public   override  RouteData GetRouteData(HttpContextBase httpContext)
   21:      {
   22:          Uri uri = httpContext.Request.Url;
   23:          Uri baseAddress =  new  Uri( string .Format( "{0}://{1}" , uri.Scheme, uri.Authority));
   24:          UriTemplateMatch match =  this .UriTemplate.Match(baseAddress, uri);
   25:           if  ( null  == match)
   26:          {
   27:               return   null ;
   28:          }
   29:          RouteData routeData =  new  RouteData();
   30:          routeData.RouteHandler =  this .RouteHandler;
   31:          routeData.Route =  this ;
   32:           foreach  ( string  name  in  match.BoundVariables.Keys)
   33:          { 
   34:              routeData.Values.Add(name,match.BoundVariables[name]);
   35:          }
   36:           foreach  (var token  in   this .DataTokens)
   37:          {
   38:              routeData.DataTokens.Add(token.Key, token.Value);
   39:          }
   40:           return  routeData;
   41:      }
   42:       public   override  VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
   43:      {
   44:          Uri uri = requestContext.HttpContext.Request.Url;
   45:          Uri baseAddress =  new  Uri( string .Format( "{0}://{1}" , uri.Scheme, uri.Authority));
   46:          Dictionary< string ,  string > variables =  new  Dictionary< string ,  string >();
   47:           foreach (var item  in  values)
   48:          {
   49:              variables.Add(item.Key, item.Value.ToString());
   50:          }
   51:   
   52:           //确定段变量是否被提供 
   53:           foreach  (var name  in   this .UriTemplate.PathSegmentVariableNames)
   54:          { 
   55:               if (! this .UriTemplate.Defaults.Keys.Any(key=>  string .Compare(name,key, true ) == 0) && 
   56:                  !values.Keys.Any(key=>  string .Compare(name,key, true ) == 0))
   57:              {
   58:                   return   null ;
   59:              }
   60:          }
   61:           //确定查询变量是否被提供 
   62:           foreach  (var name  in   this .UriTemplate.QueryValueVariableNames)
   63:          { 
   64:               if (! this .UriTemplate.Defaults.Keys.Any(key=>  string .Compare(name,key, true ) == 0) && 
   65:                  !values.Keys.Any(key=>  string .Compare(name,key, true ) == 0))
   66:              {
   67:                   return   null ;
   68:              }
   69:          }
   70:   
   71:          Uri virtualPath =  this .UriTemplate.BindByName(baseAddress, variables);
   72:           string  strVirtualPath = virtualPath.ToString().ToLower().Replace(baseAddress.ToString().ToLower(), "" );
   73:          VirtualPathData virtualPathData =   new  VirtualPathData( this , strVirtualPath);
   74:           foreach  (var token  in   this .DataTokens)
   75:          {
   76:              virtualPathData.DataTokens.Add(token.Key, token.Value);
   77:          }
   78:           return  virtualPathData;
   79:      }
   80:  }

如上面的代码片断所示,UriTemplateRoute具有UriTemplate、DataTokens和RouteHandler三个只读属性,前两个通过构造函数的参数进行初始化,后者则是在构造函数中创建的PageRouteHandler对象。

用于对入栈请求进行匹配判断的GetRouteData方法中,我们解析出基于应用的基地址并量连同请求地址作为参数调用UriTemplate的Match方法,如果返回的UriTemplateMatch对象不为Null,则意味着URL模板的模式与请求地址匹配。在匹配的情况下我们创建并返回相应的RouteData对象,否则直接返回Null。

在用于生成出栈URL的GetVirtualPath方法中,我们通过定义在URL模板中的模板(包括变量名包含在属性PathSegmentVariableNames的路径段变量和包含在QueryValueVariableNames属性的查询变量)是否在提供的RouteValueDictionary字段或者默认变量列表(通过属性Defaults表示)从判断URL模板是否与提供的变量列表匹配。在匹配的情况下通过调用UriTemplate的BindByName方法得到一个完整的Uri。由于该方法返回的是相对路径,所以我们需要将应用基地址剔除并最终创建并返回一个VirtualPathData对象。如果不匹配,则直接返回Null。

在创建的Global.asax文件中我们采用如下的代码对我们自定义的UriTemplateRoute进行注册,选用的场景还是我们上面采用的天气预报的例子。我个人具有基于UriTemplate的URI模板比针对Route的URL模板更好用,其中一点就是它在定义默认值方法更为直接。如下面的代码片断所示,我们直接将默认值定义在模板中(( "{areacode=010}/{days=2} )。

    1:   public   class  Global : System.Web.HttpApplication
    2:  {
    3:       protected   void  Application_Start( object  sender, EventArgs e)
    4:      {
    5:          UriTemplateRoute route =  new  UriTemplateRoute( "{areacode=010}/{days=2}" ,
    6:               "~/Weather.aspx" ,  new  { defualtCity =  "BeiJing" , defaultDays = 2});
    7:          RouteTable.Routes.Add( "default" , route);
    8:      }
    9:  }

在注册的路由对应的目标页面Weather.aspx的后台代码中,我们定义了如下一个GenerateUrl根据指定的区号(areacode)和预报天数(days)创建一个Url,而Url的生成直接通过调用RouteTable的Routes属性的GetVirtualPathData方法完成。生成的URL连同当前页面的RouteData的属性通过如下所示的HTML输出来。

    1:  <body>
    2:      <form id= "form1"  runat= "server" >
    3:      <div>
    4:          <table>
    5:              <tr>
    6:                  <td>Router:</td>
    7:                  <td><%=RouteData.Route !=  null ? RouteData.Route.GetType().FullName: ""  %></td>
    8:              </tr>
    9:              <tr>
   10:                  <td>RouteHandler:</td>
   11:                  <td><%=RouteData.RouteHandler !=  null ? RouteData.RouteHandler.GetType().FullName: ""  %></td>
   12:              </tr>
   13:              <tr>
   14:                  <td>Values:</td>
   15:                  <td>
   16:                      <ul>
   17:                          <% foreach  (var variable  in  RouteData.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  RouteData.DataTokens)
   29:                            {%>
   30:                          <li><%=variable.Key%>=<%=variable.Value%></li>
   31:                          <% }%>
   32:                      </ul>
   33:                  </td>
   34:              </tr>
   35:               <tr>
   36:                  <td>Generated Url:</td>
   37:                  <td>
   38:                      <%=GenerateUrl( "0512" ,3)%>
   39:                  </td>
   40:              </tr>
   41:          </table>
   42:      </div>
   43:      </form>
   44:  </body>

由于注册的URL模板所包含的段均由具有默认值的变量构成,所以当我们请求根地址时,会自动路由到Weather.aspx。下图是我们在浏览器访问应用根目录的截图,上面显示了我们注册的UriTemplateRoute生成的RouteData的信息和生成URL(/0512/3)。

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

作者: Leo_wl

    

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

    

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

版权信息

查看更多关于ASP.NET路由系统实现原理:HttpHandler的动态映射的详细内容...

  阅读:41次