惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 司徒正美
大猫的无限游戏
大猫的无限游戏
腾讯CDC
J
Java Code Geeks
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
V
Visual Studio Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
博客园 - 聂微东
阮一峰的网络日志
阮一峰的网络日志
美团技术团队
云风的 BLOG
云风的 BLOG
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
U
Unit 42
雷峰网
雷峰网
B
Blog RSS Feed
博客园_首页
量子位
F
Fortinet All Blogs
罗磊的独立博客
H
Hackread – Cybersecurity News, Data Breaches, AI and More
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Check Point Blog

博客园 - alby

在 ASP.NET Core Web API 中处理 Patch 请求 一种有界队列(Bounded Buffer)的实现 基于 Mediasoup 的 Abp vNext 视频会议模块 使用 ASP.NET Core 作为 mediasoup 的信令服务器 ASP.NET Core 的 `Core` 有几种写法? ASP.NET Core MVC 授权的扩展:自定义 Authorize Attribute 和 IApplicationModelProvide OrchardCore 如何实现模块化( Modular )和 Multi-Tenancy [iOS]关于状态栏(UIStatusBar)的若干问题 [iOS]关于视频方向的若干问题 --2015-06-24-- Orchard源码分析(7.2):Controller相关 Orchard源码分析(7.1):Routing(路由)相关 Orchard源码分析(7):ASP.NET MVC相关 Orchard源码分析(4.4):Orchard.Caching.CacheModule类 Orchard源码分析(5.3):EndRequest事件处理(DefaultOrchardHost.EndRequest方法) Orchard源码分析(5.2):BeginRequest事件处理(DefaultOrchardHost.BeginRequest方法) Orchard源码分析(5.1):Host初始化(DefaultOrchardHost.Initialize方法) Orchard源码分析(5):Host相关(Orchard.Environment.DefaultOrchardHost类) Orchard源码分析(4.3):Orchard.Events.EventsModule类(Event Bus)
Orchard源码分析(6):Shell相关
alby · 2012-10-18 · via 博客园 - alby

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(多个)

参考资料: