概述
在Orchard中,提出子站点(Tenant)的概念,目的是为了增加站点密度,即一个应用程序域可以有多个子站点。

Shell是子站点(Tenant)级的单例,换句话说Shell代表了子站点。对比来看,Host是应用程序域级的单例,代表了Orchard应用程序。本文将分析Shell相关的各种类型。

 
一、获取ShellSettings
在 DefaultOrchardHost类的CreateAndActivateShells方法中,由Shell设置管理器 ShellSettingsManager从~/App_Data/Sites目录的一级子目录中搜索Settings.txt文件,然后通过Shell 设置序列化器ShellSettingsSerializer将其反序列化为Shell配置ShellSettings对象。ShellSettings 记录了Shell的名称、DataProvider、数据库连接字符串、Host名称、Url前缀、加密算法、加密Key、可用的Themes、 Tenant的状态等信息,更详细的请看类定义。
            // is there any tenant right now ?
            var allSettings = _shellSettingsManager.LoadSettings().ToArray();
 
ShellSettingsManager类及ShellSettingsSerializer类都挺简单,这里就不分析了。 
   
二、创建ShellContext及DefaultOrchardShell
ShellContextFactory根据ShellSettings创建Shell上下文Shellontext对象。ShellContextFactory类有三个公开的方法:
        ShellContext CreateShellContext(ShellSettings settings);
        ShellContext CreateSetupContext(ShellSettings settings);
        ShellContext CreateDescribedContext(ShellSettings settings, ShellDescriptor shellDescriptor);
在 DefaultOrchardHost的CreateAndActivateShells方法中,如果ShellSettingsManager没有获取 到任何ShellSettings对象,则会new一个Name为"Default"的ShellSettings对象,并将其作为参数调用 ShellContextFactory的ShellSetupContext方法来创建一个用于安装的ShellContext对象。
否 则,如果ShellSettingsManager如果获取到一个或多个ShellSettings对象,则会调用 ShellContextFactory的CreateShellContext方法分别创建对应的ShellContext对象。也就是说前面的 ShellSettings对象和Settings.txt文件是一对一对关系,而ShellContext对象和ShellSettings对象也是一 对一的关系。
CreateDescribedContext方法会在"操作引擎"DefaultProcessingEngine中得以使用。关于"操作引擎"我们在适当的时候来分析,本文暂不讨论。
上面分析的相关源码:
            // load all tenants, and activate their shell
            if (allSettings.Any()) {
                foreach (var settings in allSettings) {
                    try {
                        var context = CreateShellContext(settings);
                        ActivateShell(context);
                    }
                    catch(Exception e) {
                        Logger.Error(e, "A tenant could not be started: " + settings.Name);
                    }
                }
            }
            // no settings, run the Setup
            else {
                var setupContext = CreateSetupContext();
                ActivateShell(setupContext);
            }
        //......
        ShellContext CreateSetupContext() {
            Logger.Debug( "Creating shell context for root setup" );
            return _shellContextFactory.CreateSetupContext(new ShellSettings { Name = ShellSettings.DefaultName });
        }
 
        ShellContext CreateShellContext(ShellSettings settings) {
            if (settings.State.CurrentState == TenantState .State.Uninitialized) {
                Logger.Debug( "Creating shell context for tenant {0} setup" , settings.Name);
                return _shellContextFactory.CreateSetupContext(settings);
            }
 
            Logger.Debug( "Creating shell context for tenant {0}" , settings.Name);
            return _shellContextFactory.CreateShellContext(settings);
        }
 
 
接下来分析ShellContextFactory创建ShellContext的步骤。
首先来看ShellContext的定义:
     public class ShellContext {
        public ShellSettings Settings { get; set; }
        public ShellDescriptor Descriptor { get; set; }
        public ShellBlueprint Blueprint { get; set; }
        public ILifetimeScope LifetimeScope { get; set; }
        public IOrchardShell Shell { get; set; }
    }
 
Settings属性,也就是上面描述中获取或创建的Shell设置。
Descriptor属性,从数据库中获取。
Blueprint属性,Shell"蓝图"。
LifetimeScope属性,可以简单理解为Autofac容器创建的子容器,作用域是Shell,即多个Shell之间不会共享。
Shell属性,一般就是DefaultOrchardHost型对象。
 
ShellContextFactory类的CreateShellContext方法创建ShellContext大致包括这些步骤:

1、从Shell描述缓存中提取(Fetch)ShellDescriptor对象——使用ShellDescriptorCache类:

           var knownDescriptor = _shellDescriptorCache.Fetch(settings.Name);
            if (knownDescriptor == null ) {
                Logger.Information( "No descriptor cached. Starting with minimum components." );
                knownDescriptor = MinimumShellDescriptor();
            }
 
通 过调用在Shell描述缓存ShellDescriptorCache的Fetch方法,尝试从~/App_Data/cache.dat这个xml文件 中检索ShellSettings.Name对应的节点,然后将节点反序列化为ShellDescriptor对象。如果缓存中不存在对应的节点,则调用 ShellContextFactory的私有静态方法MinimumShellDescriptor获取一个"最小"的ShellDescriptor对象,其序列号(即SerialNumber属性)为-1:
        private static ShellDescriptor MinimumShellDescriptor() {
            return new ShellDescriptor {
                SerialNumber = -1,
                Features = new[] {
                    new ShellFeature {Name = "Orchard.Framework"},
                    new ShellFeature {Name = "Settings"},
                },
                Parameters = Enumerable.Empty<ShellParameter >(),
            };
        }
  
ShellDescriptor 的作用是什么呢?Orchard加载了很多的扩展(Modules和Themes),但是Shell并不一定会全用上。ShellDescriptor包 含Shell要用到的扩展(这里成为Feature)名称(封装在ShellFeature类中)的集合,还包含参数(封装在ShellParamter 类中)集合。
 
2、通过组合策略(Composition Strategy)进行组合(Compose)从而生成Shell蓝图ShellBlueprint——使用CompositionStrategy类
           var blueprint = _compositionStrategy.Compose(settings, knownDescriptor);
 
在 组合的过程中,会加载相关程序集,并通过反射搜索程序集中的公共类型并保存在Shell蓝图中。这些类型需要在Autofac容器中进行注册,包括 Controller(实现了IController接口的类型)、实现了IDependency接口的类型、Autofac模块(实现了 Autofac.Core.IModule接口的类型),还包括一些与数据相关的类型(*.Moduls或*.Records命名空间下、且包含一个名为 Id的可读非virtual属性、且非abstract非sealed的类型、且非实现了IContent接口或继承自 ContentPartRecord类的类型)。
 
整个组合过程需要扩展管理器(ExtensionManager)和那5个扩展加载器(ExtensionLoader)来配合。
 
3、根据ShellBuleprint创建Shell作用域容器——使用ShellContainerFactory类
            var shellScope = _shellContainerFactory.CreateContainer(settings, blueprint);
创建Autofac子容器,并在容器中注册Shell蓝图中记录的类型。
  
4、从容器中获取Shell当前使用的ShellDescriptor
            ShellDescriptor currentDescriptor;
            using (var standaloneEnvironment = shellScope.CreateWorkContextScope()) {
                var shellDescriptorManager = standaloneEnvironment.Resolve<IShellDescriptorManager>();
                currentDescriptor = shellDescriptorManager.GetShellDescriptor();
            }
 
shellDescriptorManager是一个ShellDescriptorManager实例,通过调用其GetShellDescriptor从数据库中获取ShellDescriptor。
到目前为止,已经获取了两个ShellDescriptor对象,即这里通过ShellManagercurrentDescriptor从数据库获取的currentDescriptor和上面通过ShellDescriptorCache从cache.dat中提取(Fetch)的knownDescriptor。
 
5、如果Shell当前已经在使用某个ShellDescriptor,并且与上面获取的ShellDescriptor的序列号(即SerialNumber属性)不同,则重置cache.dat中的配置,并重新生成ShellBlueprint和Shell作用域容器:
             if (currentDescriptor != null && knownDescriptor.SerialNumber != currentDescriptor.SerialNumber) {
                Logger.Information( "Newer descriptor obtained. Rebuilding shell container." );
 
                _shellDescriptorCache.Store(settings.Name, currentDescriptor);
                blueprint = _compositionStrategy.Compose(settings, currentDescriptor);
                shellScope.Dispose();
                shellScope = _shellContainerFactory.CreateContainer(settings, blueprint);
            }
ShellDescriptorCache.Store会将相关信息写入到cache.dat文件中。
 
6、创建ShellContext
创建ShellContext并对相关属性赋值。特别地,会从Shell作用域的容器中获取一个DefaultOrchardShell对象赋给ShellContext的Shell属性:
            return new ShellContext {
                Settings = settings,
                Descriptor = currentDescriptor,
                Blueprint = blueprint,
                LifetimeScope = shellScope,
                Shell = shellScope.Resolve< IOrchardShell>(),
            };
 
shellScope.Resolve< IOrchardShell>()就是创建DefaultOrchardShell的过程。
 
与CreateShellContext方法类似,CreateSetupContext方法创建ShellContext大致包括这些步骤:
1、手工创建一个Name为"Default"的ShellSettings对象;
2、创建一个ShellDescriptor对象;
3、通过组合策略(Composition Strategy)进行组合(Compose)生成Shell蓝图ShellBlueprint;
4、创建Shell作用域容器,与CreateShellContext方法一样
5、创建ShellContext,与CreateShellContext方法一样
 
三、激活Shell
 
在DefaultOrchardHost中创建ShellContext成功后,会将后者作为参数调用ActivateShell方法:
            // load all tenants, and activate their shell
            if (allSettings.Any()) {
                foreach (var settings in allSettings) {
                    try {
                        var context = CreateShellContext(settings);
                        ActivateShell(context);
                    }
                    catch(Exception e) {
                        Logger.Error(e, "A tenant could not be started: " + settings.Name);
                    }
                }
            }
            // no settings, run the Setup
            else {
                var setupContext = CreateSetupContext();
                ActivateShell(setupContext);
            }
         //......
         /// <summary>
        /// Start a Shell and register its settings in RunningShellTable
        /// </summary>
        private void ActivateShell(ShellContext context) {
            Logger.Debug( "Activating context for tenant {0}" , context.Settings.Name);
            context.Shell.Activate();
 
            _shellContexts = (_shellContexts ?? Enumerable.Empty<ShellContext >()).Union(new [] {context});
            _runningShellTable.Add(context.Settings);
        }
 
_shellContexts是一个保存了ShellContext的集合。前面提到过,如果该集合为null,则会在BeginRequest的时候触发加载扩展的操作。
_runningShellTable是一个保存了ShellSettings的集合。当Http请求进来时,ASP.NET MVC会搜索与Shell对应的路由,详见ShellRoute类。
context.Shell是一个DefaultOrchardShell实例,调用其Activate方法激活Shell:
        /// 该方法位于DefaultOrchardShell类中
        public void Activate() {
            _routePublisher.Publish(_routeProviders.SelectMany(provider => provider.GetRoutes()));
            _modelBinderPublisher.Publish(_modelBinderProviders.SelectMany(provider => provider.GetModelBinders()));
 
            _sweepGenerator.Activate();
 
            using (var events = _eventsFactory()) {
                events.Value.Activated();
            }
        }
  
_routePublisher和_modelBinderPublisher完成Route或ModelBinder的注册工作。Activate方法中的其余代码暂不关注。
 
相关类型:
Orchard.Environment.Configuration.ShellSettings
Orchard.Environment.Configuration.DefaultShellSettingsManager : IShellSettingsManager
Orchard.Environment.ShellBuilders.ShellContext
Orchard.Environment.ShellBuilders.ShellContextFactory : IShellContextFactory
Orchard.Environment.Descriptor.Models.ShellDescriptor
Orchard.Environment.Descriptor.Models.ShellFeature
Orchard.Environment.Descriptor.Models.ShellParameter
Orchard.Environment.Descriptor.ShellDescriptorCache : IShellDescriptorCache
Orchard.Environment.ShellBuilders.CompositionStrategy : ICompositionStrategy
Orchard.Environment.ShellBuilders.Models.ShellBlueprint
Orchard.Environment.ShellBuilders.Models.ShellBlueprintItem
Orchard.Environment.ShellBuilders.Models.DependencyBlueprint
Orchard.Environment.ShellBuilders.Models.ControllerBlueprint
Orchard.Environment.ShellBuilders.Models.RecordBlueprint
Orchard.Environment.DefaultOrchardHost : IOrchardHost
Orchard.Environment.State.DefaultProcessingEngine: IProcessingEngine
Orchard.Mvc.Routes.RoutePublisher : IRoutePublisher
Orchard.Mvc.Routes.DefaultRouteProvider : IRouteProvider(多个)
Orchard.Mvc.ModelBinders.ModelBinderPublisher : IModelBinderPublisher
Orchard.Core.XmlRpc.Models.ModelBinderProvider : IModelBinderProvider, IModelBinder(多个)
 
 
参考资料:

Orchard源码分析(6):Shell相关的更多相关文章

  1. Orchard源码分析(5):Host相关(Orchard.Environment.DefaultOrchardHost类)

    概述 Host 是应用程序域级的单例,代表了Orchard应用程序.其处理应用程序生命周期中的初始化.BeginRequest事件.EndRequest事件等. 可以简单理解为HttpApplicat ...

  2. Orchard源码分析(1):Orchard架构

      本文主要参考官方文档"How Orchard works"以及Orchardch上的翻译.   源码分析应该做到庖丁解牛,而不是以管窥豹或瞎子摸象.所以先对Orchard架构有 ...

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

    概述 关于ASP.NET MVC中路由有两个基本核心作用,一是通过Http请求中的Url参数等信息获取路由数据(RouteData),路由数据包含了area.controller.action的名称等 ...

  4. Orchard源码分析(5.1):Host初始化(DefaultOrchardHost.Initialize方法)

    概述 Orchard作为一个可扩展的CMS系统,是由一系列的模块(Modules)或主题(Themes)组成,这些模块或主题统称为扩展(Extensions).在初始化或运行时需要对扩展进行安装:De ...

  5. Orchard源码分析(2):Orchard.Web.MvcApplication类(Global)

    概述 分析一个的ASP.NET项目源码,首先可以浏览其项目结构,大致一窥项目其全貌,了解项目之间的依赖关系.其次可以浏览Web.config和Global.asax文件,找到应用程序的入口点. 本 文 ...

  6. Orchard源码分析(4.4):Orchard.Caching.CacheModule类

    概述 CacheModule也是一个Autofac模块.   一.CacheModule类 CacheModule将DefaultCacheManager注册为ICacheManager:       ...

  7. Orchard源码分析(4.3):Orchard.Events.EventsModule类(Event Bus)

    概述 采用Event Bus模式(事件总线),可以使观察者模式中的观察者和被观察者实现解耦. 在.Net 中使用观察者模式,可以使用事件(委托)和接口(类).Orchard Event  Bus使用的 ...

  8. Orchard源码分析(3):Orchard.WarmupStarter程序集

    概述 Orchard.WarmupStarter程序集包含三个类:WarmupUtility.WarmupHttpModule和Starter<T>.该程序集主要为Orchard应用启动初 ...

  9. Orchard源码分析(1):插件式的支持——模块和主题

    在Orchard,模块和主题都是可以插拔式的,在源码处理时,用类型(参考:DefaultExtensionTypes)区分,都没太大的本质区别,以下都称做模块. 插件的支持,实现分以下几步: 搜集模块 ...

随机推荐

  1. RFID标签

    定义: RFID无线射频识别是一种非接触式的自动识别技术,它通过射频信号自动识别目标对象并获取相关数据,识别工作无须人工干预,可工作于各种恶劣环境.RFID技术可识别高速运动物体并可同时识别多个电子标 ...

  2. selenium+eclispse里代码备注

    1.火狐.谷歌和IE浏览器引擎都要重新下载selenium官网引擎,并设置路径才可以支持selenium3 而狐火用自己的引擎不用设置路径既可以支持selenium2也支持selenium3,谷歌和I ...

  3. 【POJ 1269】判断两直线相交

    题 利用叉积解方程 #include <cstdio> #define MAX 1<<31 #define dd double int xmult(dd x1,dd y1,dd ...

  4. [转]关于网络通信,byte[]和String的转换问题

    最近的项目中要使用到把byte[]类型转换成String字符串然后通过网络发送,但发现发现出去的字符串和获取的字符串虽然是一样的,但当用String的getBytes()的方法得到的byte[]跟原来 ...

  5. 匿名内部类为什么访问外部类局部变量必须是final的?

    1.内部类里面使用外部类的局部变量时,其实就是内部类的对象在使用它,内部类对象生命周期中都可能调用它,而内部类试图访问外部方法中的局部变量时,外部方法的局部变量很可能已经不存在了,那么就得延续其生命, ...

  6. 【C++实现python字符串函数库】一:分割函数:split、rsplit

    [C++实现python字符串函数库]split()与rsplit()方法 前言 本系列文章将介绍python提供的字符串函数,并尝试使用C++来实现这些函数.这些C++函数在这里做单独的分析,最后我 ...

  7. 【BZOJ-1076】奖励关 概率与期望 + 状态压缩DP

    1076: [SCOI2008]奖励关 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1602  Solved: 891[Submit][Status ...

  8. 订阅Linux内核的邮件列表

    1.发送订阅邮件 注意:一定不要使用像Foxmail这样的第三方邮件客户端,因为发送的邮件会转码,导致订阅不成功,推荐使用Gmail,直接登录进去发送. 发送邮件内容: 接收人:majordomo@v ...

  9. dotnet反编译工具大全

    反编译不是为了破解软件,而是在开发时更好的读懂程序干了什么,比如MVC的源码,如果使用1和4的VS插件能更好的进行断点跟踪. 常用,效率最高: 1.[.NET Reflector]首选,能比较好的反编 ...

  10. VS生成事件宏$(TargetPath) 一直为空

    在接手以前的项目的时候,遇见一个很奇怪的问题,我在一个项目的类库里面,使用了生成实现,如下: copy /Y $(TargetPath) $(SolutionDir)..\ copy /Y $(Tar ...