Orchard源码分析(7.1):Routing(路由)相关

编程入门 行业动态 更新时间:2024-10-12 01:22:23

Orchard源码分析(7.1):Routing(<a href=https://www.elefans.com/category/jswz/34/1771390.html style=路由)相关"/>

Orchard源码分析(7.1):Routing(路由)相关

概述 关于ASP.NET MVC中路由有两个基本核心作用,一是通过Http请求中的Url参数等信息获取路由数据(RouteData),路由数据包含了area、controller、action的名称等信息。只有获取了匹配的路由数据,才有可能转入ASP.NET MVC管道;二是根据由规则生成Url,比如要根据某些数据生成View上显示的链接。 Orchard对路由进行扩展主要基于如下原因: (1)、路由定义在各个模块中。在Orchard应用程序初始化时将分散在各个模块的路由定义收集起来统一注册。 (2)、路由定义一次,对于多Shell系统,则会被多次注册以匹配Shell的前缀。 (3)、当请求进入时需要确认进入了哪个Shell,并且在成Url时也需要加上Shell的Url前缀。 (4)、将WorkContextAccessor放入路由数据的DataTokens中。WorkContextAccessor工作上下文访问器封装了HTTP上下文、Autofa容器等信息。 (5)、重置IRouteHandler和IHttpHandler,以包含WorkContextAccessor、包含Shell的配置(ShellSettings)、包含应用程序域中正在运行的Shell(RunningShellTable)、设置SessionState等。 请留意下文描述中System.Web.Routing.RouteBase、Route、RouteData、Orchard.Mvc.Routes.ShellRoute、Orchard.Mvc.Routes. RouteDescriptor及Orchard.Mvc.Routes.Http RouteDescriptor之间的关系。 一、路由的定义 如果Orchard模块需要路由,并不是在Global.asax.cs等地方直接配置,而是先将路由定义在模块源码一个或多个实现了 Orchard.Mvc.Routes.IRouteProvide.IRouteProvider接口或 Orchard.WebApi.Routes.IHttpRouteProvider的类的IEnumerable<RouteDescriptor> GetRoutes()方法中。 如Orchard.Blogs模块就定义了一个名为Routes的类,该类就实现了IRouteProvider接口,主要关注GetRoutes方法:          // 以下代码来在Orchard.Blogs.Routes类          public  IEnumerable < RouteDescriptor > GetRoutes() {              return  new  [] {                               new  RouteDescriptor  {                                                      Route =  new  Route  (                                                           "Admin/Blogs/Create" ,                                                           new  RouteValueDictionary  {                                                                                       { "area" ,  "Orchard.Blogs"  },                                                                                       { "controller"  ,  "BlogAdmin" },                                                                                       { "action" ,  "Create"  }                                                                                   },                                                           new  RouteValueDictionary  (),                                                           new  RouteValueDictionary  {                                                                                       { "area" ,  "Orchard.Blogs"  }                                                                                   },                                                           new  MvcRouteHandler  ())                                                  }, //...... GetRoutes方法返回一个路由描述RouteDescriptor对象集合。 RouteDescriptor类包装了一个RouteBase类,并有Name和Priority属性:      public  class  RouteDescriptor  {          public  string  Name {  get ;  set ; }          public  int  Priority {  get ;  set ; }          public  RouteBase  Route {  get ;  set ; }          public  SessionStateBehavior  SessionState {  get ;  set ; }     } 一般在定义路由时用到的是Route类,它继承了RouteBase类。 通过Priority属性,我们可以更好的控制路由的注册顺序,而不是按定义的先后顺序进行注册。 在路由注册时,通过一系列的RouteDescriptor对象就够获取到对应的RouteBase对象了。 IHttpRouteProvider接口的实现类的作用类似,只是专为WebApi服务而已。有兴趣的可以看看Orchard.Mvc.Routes.StandardExtensionRouteProvider类,顺便也留意一下HttpRouteDescriptor:RouteDescriptor类。 二、路由的注册 在Shell被激活时,会将分散到不同的模块的路由收集起来,并由RoutePublisher注册到全局路由表中:          // 以下代码来在Orchard.Environment.DefaultOrchardShell类         public  void  Activate() {             var allRoutes = new List< RouteDescriptor>();             allRoutes.AddRange(_routeProviders.SelectMany(provider => provider.GetRoutes()));             allRoutes.AddRange(_httpRouteProviders.SelectMany(provider => provider.GetRoutes()));               _routePublisher.Publish(allRoutes);             _modelBinderPublisher.Publish(_modelBinderProviders.SelectMany(provider => provider.GetModelBinders()));                using  ( var  events = _eventsFactory()) {                 events.Value.Activated();             }               _sweepGenerator.Activate();         } _routeProviders是一个IEnumerable< IRouteProvider>型的私有字段,Autofac在创建DefaultOrchardShell对象时会通过构造器注入的方式初始化该字段。实际上就是相应Shell需要用到的各个模块中的的IRouteProvider对象,通过调用IRouteProvider.GetRoutes方法则可将RouteDescriptor对象收集起来。  _httpRouteProviders是一个IEnumerable< IHttpRouteProvider>型私有字段,实际上IHttpRouteProvider接口IRouteProvider接口完全一样。_httpRouteProviders和_routeProviders的初始化方式也一样。不同的是_httpRouteProviders是为WebApi服务的。 _routePublisher是一个 Orchard.Mvc.Routes.RoutePublisher对象,其Publish方法中,将 RouteDescriptor对象对应的RouteBase(一般为Route)对象,包装成ShellRoute对象注册到MVC的全局路由表中:          // 以下代码来在Orchard.Mvc.Routes.RoutePublisher类            public  void  Publish( IEnumerable  < RouteDescriptor > routes) {              var  routesArray = routes                 .OrderByDescending(r => r.Priority)                 .ToArray();                // this is not called often, but is intended to surface problems before              // the actual collection is modified              var  preloading =  new  RouteCollection ();              foreach  ( var  routeDescriptor  in  routesArray) {                    // extract the WebApi route implementation                  var  httpRouteDescriptor = routeDescriptor  as  HttpRouteDescriptor ;                  if  (httpRouteDescriptor !=  null  ) {                      var  httpRouteCollection =  new  RouteCollection ();                     httpRouteCollection.MapHttpRoute(httpRouteDescriptor.Name, httpRouteDescriptor.RouteTemplate, httpRouteDescriptor.Defaults);                     routeDescriptor.Route = httpRouteCollection.First();                 }                   preloading.Add(routeDescriptor.Name, routeDescriptor.Route);             }                                using  (_routeCollection.GetWriteLock()) {                  // existing routes are removed while the collection is briefly inaccessable                  var  cropArray = _routeCollection                     .OfType<  ShellRoute >()                     .Where(sr => sr.ShellSettingsName == _shellSettings.Name)                     .ToArray();                    foreach ( var  crop  in  cropArray) {                     _routeCollection.Remove(crop);                 }                    // new routes are added                  foreach  ( var  routeDescriptor  in  routesArray) {                      // Loading session state information.                      var  defaultSessionState =  SessionStateBehavior  .Default;                        ExtensionDescriptor  extensionDescriptor =  null  ;                      if (routeDescriptor.Route  is  Route ) {                          object  extensionId;                          var  route = routeDescriptor.Route  as  Route ;                          if (route.DataTokens !=  null  && route.DataTokens.TryGetValue( "area" ,  out  extensionId) ||                            route.Defaults !=  null  && route.Defaults.TryGetValue( "area" ,  out  extensionId)) {                             extensionDescriptor = _extensionManager.GetExtension(extensionId.ToString());                         }                     }                      else  if  (routeDescriptor.Route  is  IRouteWithArea ) {                          var  route = routeDescriptor.Route  as  IRouteWithArea ;                         extensionDescriptor = _extensionManager.GetExtension(route.Area);                     }                        if  (extensionDescriptor !=  null  ) {                          // if session state is not define explicitly, use the one define for the extension                          if  (routeDescriptor.SessionState ==  SessionStateBehavior .Default) {                              Enum .TryParse(extensionDescriptor.SessionState,  true  /*ignoreCase*/ ,  out  defaultSessionState);                         }                     }                        // Route-level setting overrides module-level setting (from manifest).                      var  sessionStateBehavior = routeDescriptor.SessionState ==  SessionStateBehavior .Default ? defaultSessionState : routeDescriptor.SessionState ;                       var shellRoute = new ShellRoute(routeDescriptor.Route, _shellSettings, _workContextAccessor, _runningShellTable) {                         IsHttpRoute = routeDescriptor is HttpRouteDescriptor ,                         SessionState = sessionStateBehavior                     };                     _routeCollection.Add(routeDescriptor.Name, shellRoute);                 }             }         } ShellRoute类通过装饰器模式包装了一个System.Web.Routing.RouteBase类,其本身也是继承自RouteBase类。 要特别留意创建ShellRoute对象时为构造函数提供的几个参数: routeDescriptor.Route:ShellRoute所包含的Route。
_shellSettings:ShellRoute对应的ShellSettings。 _workContextAccessor:WorkContextAccessor是Shell级的单例,其在WorkContextModule中被注册。它包装了一个Shell相关的Autofac子容器,通过该容器可以Resolve出Shell作用域的对象。 _runningShellTable:正在运行的Shell对应的ShellSettings表。 三、路由映射——根据请求路径查找匹配的路由数据(RouteData)   从Url角度上讲,怎么区分两个Shell呢?首先两个Shell可以拥有不同的域名,或者拥有相同的域名但不同的Url前缀。如: (1)、其中一个Shell无域名 Shell 1 - 无 Shell 2 - www.yourdomain2 (2)、不同的域名 Shell 1 - www.yourdomain1 Shell 2 - www.yourdomain2 (3)、相同的域名,不同的Url前缀 Shell 1 - www.yourdomain1/abc Shell 2 - www.yourdomain1/def (4)、相同的域名,只有一个Shell的Url前缀 Shell 1 - www.yourdomain1 Shell 2 - www.yourdomain1/def 这种情况会先检查Url是否匹配Shell 2,然后再检查是否匹配Shell 1。Url前缀长度越长,越优先检查。 引申: Shell 1 - www.yourdomain1/abc/def Shell 2 - www.yourdomain1/abc (5)、一个Shell可以对应单个或多个域名 Shell 1 - www.yourdomain1 Shell 2 - www.yourdomain2和 www.yourdomain3 (6)、更复杂的配置 为了方便分析,这里我们假设Orchard中配置了两个Shell,ShellSettings设置如下:
Shell 1:ShellSettings.RequestUrlHost ="www.yourdomain1",ShellSettings.RequestUrlPrefix=String.Empty Shell 2:ShellSettings.RequestUrlHost ="www.yourdomain2",ShellSettings.RequestUrlPrefix="abc" 并且某模块被这两个Shell使用,该模块的Routes: IRouteProvider类中定义了一个匹配"{controller}/{action}"的路由。需要注意一点,虽然这里只定义一个路由,但是这里两个Shell都会用到,所以会被包装成两个ShellRoute对象注册到全局路由表中。 再假设一个新的Http请求进入,Url是: 首先System.Web.Routing.UrlRouteModule会遍历全局路由表中的路由,期待获取一个RouteData对象。当遍历到我们刚刚注册的路由时,会调用路由的GetRouteData方法:          // 以下代码来在Orchard.Mvc.Routes.ShellRoute类          public override RouteData GetRouteData( HttpContextBase httpContext) {             // locate appropriate shell settings for request             var settings = _runningShellTable.Match(httpContext);               // only proceed if there was a match, and it was for this client             if (settings == null || settings.Name != _shellSettings.Name)                 return null ;               var effectiveHttpContext = httpContext;             if (_urlPrefix != null )                 effectiveHttpContext = new UrlPrefixAdjustedHttpContext (httpContext, _urlPrefix);               var routeData = _route.GetRouteData(effectiveHttpContext);             if (routeData == null )                 return null ;               // push provided session state behavior to underlying MvcHandler             effectiveHttpContext.SetSessionStateBehavior(SessionState);               // otherwise wrap handler and return it             routeData.RouteHandler = new RouteHandler (_workContextAccessor, routeData.RouteHandler, SessionState);             routeData.DataTokens[ "IWorkContextAccessor" ] = _workContextAccessor;               if (IsHttpRoute) {                 routeData.Values[ "IWorkContextAccessor" ] = _workContextAccessor; // for WebApi             }                         return routeData;         } Shell被成功激活后,其对应的ShellSettings会存入在一个RunningShellTable对象中。在这里也就是_runningShellTable变量。 根据传入的Url,找到匹配的ShellSettings存入局部变量_settings:              var  settings = _runningShellTable.Match(httpContext); 下面看看Match的过程:          ///  该方法位于Orchard.Environment.RunningShellTable类中         public  ShellSettings  Match( string  host,  string  appRelativePath) {              var  hostLength = host.IndexOf( ':'  );              if  (hostLength != -1)                 host = host.Substring(0, hostLength);                var  mostQualifiedMatch = _shellsByHost                 .Where(group => host.EndsWith(group.Key,  StringComparison .OrdinalIgnoreCase))                 .SelectMany(group => group                     .OrderByDescending(settings => (settings.RequestUrlPrefix ??  string .Empty).Length))                     .FirstOrDefault(settings => settings.State.CurrentState !=  TenantState . State  .Disabled && appRelativePath.StartsWith( "~/"  + (settings.RequestUrlPrefix ??  string .Empty),  StringComparison .OrdinalIgnoreCase));               return mostQualifiedMatch ?? _fallback;         } 所以  2。 GetRouteData方法接下来有个判断:              if  (settings ==  null  || settings.Name != _shellSettings.Name)                  return  null  ; settings可能为null这好理解,但其Name值为什么可能不相等呢?请留意RunningShellTable.Match方法的最后一行的_fallback变量,这里就不再详述。 如果Shell包含Url前缀,则调整HttpContext:              var  effectiveHttpContext = httpContext;              if  (_urlPrefix !=  null  )                 effectiveHttpContext =  new  UrlPrefixAdjustedHttpContext  (httpContext, _urlPrefix);   _urlPrefix是一个 Orchard.Mvc.Routes.UrlPrefix 对象,它包装了一个用来表示Shell的Url前缀 字符串 。如果RoutePublisher在创建ShellRoute时,传入的_shellSettings参数的RequestUrlPrefix属性不为null或空,则 _urlPrefix不会为null。 UrlPrefix类有两个重要的方法:RemoveLeadingSegments 和PrependLeadingSegments。如果 _urlPrefix包装的 Url前缀 字符串为"abc",则 _urlPrefix.RemoveLeadingSegments("~/abc/home/index")返回的值是"~/abc/home/index",而 _urlPrefix.PrependLeadingSegments("~/home/index")返回的值是"~/abc/home/index"。 UrlPrefixAdjustedHttpContext类最主要的目的是替换掉原来的HttpRequest,以使得HttpRequest的AppRelativeCurrentExecutionFilePath属性能够返回一个去掉Url前缀的值。这样做得目的是为了能够按"常规"方式获取到RouteData。 如 ShellRoute的 RequestUrlPrefix属性值为"abc",请求的 Url是: 则AppRelativeCurrentExecutionFilePath返回的值是: ~/home/index _route.GetRouteData方法的调用,也就是刚才说的"常规"方式:              var  routeData = _route.GetRouteData(effectiveHttpContext);              if  (routeData ==  null  )                  return  null  ;   GetRouteData最后的代码也简单:              // push provided session state behavior to underlying MvcHandler             effectiveHttpContext.SetSessionStateBehavior(SessionState);               // otherwise wrap handler and return it             routeData.RouteHandler = new RouteHandler (_workContextAccessor, routeData.RouteHandler, SessionState);             routeData.DataTokens[ "IWorkContextAccessor" ] = _workContextAccessor;               if (IsHttpRoute) {                 routeData.Values[ "IWorkContextAccessor" ] = _workContextAccessor; // for WebApi             } 这里的RouteHandler类是ShellRoute的私有嵌套类,其通过装饰器模式包装了一个IRouteHandler对象。相关类型还有私有嵌套类HttpHandler和HttpAsyncHandler。RouteHandler是为了Autofac容器的应用到 IHttpHandler中。 在上面提到的Orchard.Blogs.Routes类中,定义的Route的RouteHandler是MvcRouteHandler,这里重新包装成RouteHandler对象再赋给routeData的RouteHandler属性。 后面再将_workContextAccessor保存进routeData的DataTokens中。 四、根据路由规则生成Url          public override VirtualPathData GetVirtualPath( RequestContext requestContext, RouteValueDictionary values) {             // locate appropriate shell settings for request             var settings = _runningShellTable.Match(requestContext.HttpContext);               // only proceed if there was a match, and it was for this client             if (settings == null || settings.Name != _shellSettings.Name)                 return null ;               var effectiveRequestContext = requestContext;             if (_urlPrefix != null )                 effectiveRequestContext = new   RequestContext ( new UrlPrefixAdjustedHttpContext (requestContext.HttpContext, _urlPrefix), requestContext.RouteData);               var virtualPath = _route.GetVirtualPath(effectiveRequestContext, values);             if (virtualPath == null )                 return null ;               if (_urlPrefix != null )                 virtualPath.VirtualPath = _urlPrefix.PrependLeadingSegments(virtualPath.VirtualPath);               return virtualPath;         } 前面几行代码和GetRouteData类似,关注点在UrlPrefixAdjustedHttpContext类和UrlPrefix类,在分析GetRouteData方法时已有简单分析。 相关类型: Orchard.Mvc.Routes.ShellRoute : RouteBase, IRouteWithArea Orchard.Mvc.Routes.RouteDescriptor Orchard.Mvc.Routes.HttpRouteDescriptor Orchard.Mvc.Routes.IRouteProvider : IDependency Orchard.Mvc.Routes.IHttpRouteProvider : IDependency Orchard.Mvc.Routes.DefaultRouteProvider:IRouteProvider  Orchard.Mvc.Routes.StandardExtensionRouteProvider:IRouteProvider  Orchard.Mvc.Routes.RoutePublisher : IRoutePublisher Orchard.Mvc.Routes.UrlPrefix Orchard.Mvc.Routes.UrlPrefixAdjustedHttpContext Orchard.Environment.RunningShellTable : IRunningShellTable Orchard.Environment.WorkContextAccessor : IWorkContextAccessor Orchard.WorkContext

转载于:.html

更多推荐

Orchard源码分析(7.1):Routing(路由)相关

本文发布于:2024-03-23 15:01:59,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1739538.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:路由   源码   Orchard   Routing

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!