博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Orchard源码分析(6):Shell相关
阅读量:5128 次
发布时间:2019-06-13

本文共 9407 字,大约阅读时间需要 31 分钟。

概述

在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(多个)
参考资料:

转载于:https://www.cnblogs.com/alby/archive/2012/10/18/orchard-shell.html

你可能感兴趣的文章
记录:Android中StackOverflow的问题
查看>>
导航,头部,CSS基础
查看>>
[草稿]挂载新硬盘
查看>>
[USACO 2017 Feb Gold] Tutorial
查看>>
关于mysql中GROUP_CONCAT函数的使用
查看>>
OD使用教程20 - 调试篇20
查看>>
Java虚拟机(JVM)默认字符集详解
查看>>
Java Servlet 过滤器与 springmvc 拦截器的区别?
查看>>
(tmp >> 8) & 0xff;
查看>>
linux命令之ifconfig详细解释
查看>>
NAT地址转换
查看>>
Nhibernate 过长的字符串报错 dehydration property
查看>>
Deque - leetcode 【双端队列】
查看>>
人物角色群体攻击判定(一)
查看>>
一步步学习微软InfoPath2010和SP2010--第九章节--使用SharePoint用户配置文件Web service(2)--在事件注册表单上创建表单加载规则...
查看>>
gulp插件gulp-ruby-sass和livereload插件
查看>>
免费的大数据学习资料,这一份就足够
查看>>
clientWidth、clientHeight、offsetWidth、offsetHeight以及scrollWidth、scrollHeight
查看>>
MySQL(一)
查看>>
企业级应用与互联网应用的区别
查看>>