请注意,本站并不支持低于IE8的浏览器,为了获得最佳效果,请下载最新的浏览器,推荐下载 Chrome浏览器
欢迎光临。交流群:166852192

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


概述

Orchard作为一个可扩展的CMS系统,需要在初始化或运行时加载一些模块(Modules)或主题(Themes),这些模块或主题统称扩展(Extensions)在初始化过程中会对扩展进行设置:DefaultOrchardHost.SetupExtensions方法。
当添加新的扩展、删除扩展、修改扩展源码后,需要通知扩展加载器(Extension Loader)重新加载或完成一些清理工作,所以需要进行监视:DefaultOrchardHost.MonitorExtensions方法。
Orchard是一个多租户(Tenant)系统,也就是我们通常所是说的子站点,它允许一个Orchard应用程序中包含多个不同域名的子站点。每个子站点对应一个Shell,需要创建并激活:DefaultOrchardHost.CreateAndActivateShells方法。


一、设置扩展:SetupExtensions方法
DefaultOrchardHost.SetupExtensions方法实际上是调用的ExtensionLoaderCoordinator.SetupExtensions方法。通过字面上看ExtensionLoaderCoordinator可以叫做扩展加载器的协调器。


设置扩展包括这些步骤:
1、创建扩展加载上下文(ExtensionLoadingContext)
首先由扩展管理器(Extension Manager,默认是ExtensionManager类)搜索和收集(Harvest)扩展。它在~/Modules、~/Core和~/Themes三个目录的所有一级子目录中搜索Module.txt和Theme.txt文件。默认采用并行(Parallel)的方式以提高效率,但可以通过在配置文件HostComponents.config中设置Orchard.Caching.DefaultParallelCacheContext类型的Disabled属性为false以禁用并行机制。每一个Module.txt或Theme.txt文件都会被反序列化成ExtensionDescriptor对象(扩展描述)。(相关的类型有ExtensionHarvester、ModuleFolders、CoreModuleFolders和ThemeFolders等)
扩展加载上下文类的属性虽不多,但意义复杂。属性值是经过一系列的扩展搜索、扩展探测(Probe)而来:
(1)、AvailableExtensions:List<ExtensionDescriptor>型。当前可用的扩展描述(Available Extension Descriptor)集合。
ExtensionDescriptor对象是Module.txt或Theme.txt反序列化结果。该集合根据扩展ID(也就是扩展的目录名称)和扩展之间的依赖关系两个条件排序,这类似于Vistual Studio解决方案的项目生成顺序。
(2)、PreviousDependencies:List<DependencyDescriptor>型。最后一次成功加载扩展时,扩展、扩展加载器和扩展所引用的扩展依赖描述(Extension Dependency Descriptor)集合。该属性是冗余的。如果是第一次加载扩展,该集合当然就会为空。
(3)、DeletedDependencies: List<DependencyDescriptor>型。最后一次在系统中成功被加载的,但当前已经被移除的扩展依赖描述
它通过筛选而来,用于后面删除扩展相关的程序集。如果没有对扩展进行过移除操作,该集合当然就会为空。
(4)、AvailableExtensionsProbes:IDictionary<string, IEnumerable<ExtensionProbeEntry>>型。按扩展ID分组的的扩展探测条目集合。另外具体某一个扩展对应的扩展探测条目集合(IEnumerable<ExtensionProbeEntry>)是按扩展依赖虚拟路径最后修改时间倒序、扩展加载器Order属性两个条件排序的集合。
扩展ID实际上就是扩展的目录名称。
扩展探测条目由扩展加载器探测(Probe)而来。
Orchard提供了5个扩展加载器:CoreExtensionLoader,ReferencedExtensionLoader,PrecompiledExtensionLoader,DynamicExtensionLoader,RawThemeExtensionLoader。这些扩展器探对某一个具体的扩展测出的扩展探测条目的数量,以及扩展探测条目优先级也不尽相同:
CoreExtensionLoader只会探测~/Core目录下的扩展,扩展探测条目优先级为100;
ReferencedExtensionLoader会探测引用了外部程序集的扩展,扩展探测条目优先级为100;
PrecompiledExtensionLoader会探测扩展目录下的bin子目录有<扩展ID>.dll的扩展,扩展探测条目优先级为0;
DynamicExtensionLoader会探测在扩展目录下有C#项目文件<扩展ID>.csproj的扩展,扩展探测条目优先级为0;
RawThemeExtensionLoader只会探测~/Themes目录下的扩展(即主题),扩展探测条目优先级为0。该加载器还比较特殊,如果在主题目录下有C#项目文件<主题ID>.csproj或者主题目录下的bin子目录有<主题名称>.dll,就不会继续使用该扩展探测器进行探测。
注意:一个扩展虽然会被5个扩展加载器探测,但并不一定会获取5个扩展探测条目,并且扩展最终只会被一个扩展加载器进行加载。所以扩展探测条目集合的顺序至关重要,它是决定用哪一个扩展加载器来加载对应的扩展有的重要因素(另外一个因素是当扩展引用了其他的扩展,后者采用DynamicExtensionLoader加载,则前者不能采用PrecompiledExtensionLoader来加载)。
前面提到过,决定扩展探测条目集合顺序的因素有两个:扩展依赖虚拟路径最后修改时间及扩展加载器的Order属性。
扩展依赖虚拟路径是ExtensionProbeEntry的VirtualPathDependencies属性。不同的扩展加载器对扩展依赖虚拟路径的理解是不一样的:
CoreExtensionLoader:无扩展依赖虚拟路径
ReferencedExtensionLoader:~/bin/<扩展ID>.dll文件
PrecompiledExtensionLoader:~/<Core、Modules或Themes>/<扩展ID>/bin/<扩展ID>.dll文件
DynamicExtensionLoader:首先将~/<Core、Modules或Themes>/<扩展ID>/<扩展ID>.csproj反序列化,扩展依赖路径就包含四部分:一是.csproj文件本身;二是扩展项目包含的.cs等源文件;三是扩展项目应用的第三方程序集;四是扩展项目引用的其他扩展(仅~/Modules和~/Theme目录下的扩展)的路径,这会引起递归搜索。
RawThemeExtensionLoader:无扩展依赖虚拟路径
不同的扩展加载器的Order属性值也是不一样的:
CoreExtensionLoader:10
ReferencedExtensionLoader:20
PrecompiledExtensionLoader:30
DynamicExtensionLoader:100
RawThemeExtensionLoader:10
(5)、ReferencesByName:IDictionary<string, IEnumerable<ExtensionReferenceProbeEntry>>型。按扩展ID分组的扩展引用探测条目集合。其中扩展引用探测条目由也是由扩展加载器探测而来,只有PrecompiledExtensionLoader和DynamicExtensionLoader这两种探测器有实际的功能。
PrecompiledExtensionLoader将探测出~/<Core、Modules或Themes>/<扩展ID>/bin/<扩展ID>.dll,即扩展项目引用的其他程序集。该扩展加载器探测出来的扩展引用探测条目(ExtensionReferenceProbeEntry)的Name属性值为<非扩展ID>.dll文件的不包含扩展名的文件名。
DynamicExtensionLoader将探测出~/<Core、Modules或Themes>/<扩展ID>/<扩展ID>.csproj文件中描述的、扩展项目引用的其他程序集或其他项目。该扩展加载器探测出来的扩展引用探测条目(ExtensionReferenceProbeEntry)的Name属性值,对于其他程序集为程序集的短名称,对于其他项目为<项目名称>.csproj文件的不包含扩展名的文件名。
(6)、ReferencesByModule:IDictionary<string, IEnumerable<ExtensionReferenceProbeEntry>>型。按扩展引用探测名称分组的扩展引用探测集合。ReferencesByModule与ReferenceByName类似,唯一的不同就是分组方式。
(7)、VirtualPathModficationDates:ConcurrentDictionary<string, DateTime>型。扩展对应的扩展依赖虚拟路径最后修改时间字典。该属性是冗余的。
2、从扩展加载上下文(Extension Loading Context)获取已经被移除的扩展,并通知扩展对应的扩展加载器(Extension Loader)
扩展加载器可能会把删除扩展对应的程序集的删除动作(Delete Action)放入扩展加载器上下文中,实际的删除操作是在ExtensionLoaderCoordinator类进行而不是扩展加载器。程序集可能位于~/Bin或~/App_Data/Dependencies目录。删除~/Bin目录的程序集会设置重启应用程序域标记为true;删除~/App_Data/Dependencies目录的程序集会判断程序集是否已经被应用程序域加载,如果将要删除已经被加载的程序集则设置重启应用程序域标记为true。
3、扩展加载器激活扩展(Activate Extensions)
扩展加载器可能会把扩展需要用到的程序集的复制操作(Copy Action)放入扩展加载器上下文中,实际的复制操作是在ExtensionLoaderCoordinator类进行而不是扩展加载器。程序集将被~/App_Data/Dependencies目录。
4、执行程序集复制或删除操作
5、将扩展依赖信息存储到xml文件中
6、如有必要,重启应用程序域
7、补充说明
提取几个重要细节进行补充说明:查找扩展(Look for Extensions)、探测扩展(Probe Extensions)、获取已经被移除的扩展的依赖、检测扩展引用(Probe Extension References)、激活扩展(Activate Extensions)。
(1)、搜索扩展
在~/Modules、~/Core和~/Themes三个目录的所有一级子目录中搜索Module.txt和Theme.txt文件。默认采用并行(Parallel)的方式以提高效率(可以通过在配置文件HostComponents.config中设置Orchard.Caching.DefaultParallelCacheContext类型的Disabled
属性为false以禁用并行机制)。具体的搜索工作由扩展收集器ExtensionHarvester类来完成(相关的类型有ExtensionManager、ModuleFolders、CoreModuleFolders和ThemeFolders等)。
每一个Module.txt或Theme.txt文件都会被反序列化成ExtensionDescriptor对象(扩展描述),组合成一个按扩展目录名称排序后的List<ExtensionDescriptor>对象以供后面使用。
然后根据目录名称判断是否有重复的扩展,如果有相同名称的扩展则会报异常。比如在~/Modules目录有个Lucene扩展,在~/Core或~/Themes就不能有相同名称的扩展。
有必要说一下,因为扩展加载器(Extension Loader)的原因,扩展的程序集名称请务必保持和目录名称一致。当新建一个扩展项目的时候默认就是这样,也就是说不要去修改它。
(2)、探测扩展
不同的扩展加载器有不同的探测策略,关于这方面上文已有描述。
(3)、获取已经被移除的扩展的依赖
我们知道,不管是Module还是Theme的扩展,都是独立的项目,项目除了依赖有自己程序集,还可能依赖第三方程序集或其他扩展。
~/App_Data/Dependencies/dependencies.xml扩展依赖文件保存了最后一次成功加载扩展时,扩展、扩展加载器和扩展所引用的程序集列表。
读取扩展依赖文件将之反序列化最终为一个List<DependencyDescriptor>对象(依赖描述)。
在SetupExtension方法中,会比较依赖描述和查找到的扩展,如果检测到某些扩展已经被移除,则相关的程序集dll文件也将被移除。dll移除工作由对应的扩展加载器完成。
(4)、探测扩展引用
实际上只有PrecompiledExtensionLoader和DynamicExtensionLoader这两种探测器有实际的功能。PrecompiledExtensionLoader将提取在&ldquo;~/<Core、Modules或Themes>/<扩展ID>/bin&rdquo;目录下非&ldquo;<扩展ID>.dll&rdquo;的程序集;将提取在&ldquo;~/<Modules或Themes>/<扩展ID>.csproj&rdquo;项目文件描述中引用的第三方程序集或其他扩展项目的程序集(PrecompiledExtensionLoader不会对~/Core扩展目录的核心扩展进行探测)。
(5)、激活扩展
Orchard提供了5个扩展加载器,针对不同的加载、激活策略:
CoreExtensionLoader
如果&ldquo;Module.txt&rdquo;文件来自于~/Core文件夹,CoreModuleLoader将返回来自于Orchard.Core.dll中,Orchard.Core.<扩展ID>命名空间下的所有类型。Orchard.Core.dll是一个特殊的程序集,它包含了Orchard核心模块,在Orchard框架基础上提供一些基本功能。
ReferencedExtensionLoader
在&ldquo;~/bin&rdquo;目录中查找&ldquo;Module.txt&rdquo;文件中模块名对应的程序集,如果这个程序集存在,它将加载并返回改程序集的所有类型。这种加载机用于当所有模块都是预先编译好的,并且其dll都存储在&ldquo;~/bin&rdquo;目录中的情况,这是一种典型&ldquo;ASP.NET MVC&rdquo;应用程序方式。
PrecompiledExtensionLoader
如果&ldquo;Module.txt&rdquo;文件来自于~/Core、~/Modules、~/Themes 文件夹,PrecompiledExtensionLoader将在&ldquo;~/<Core、Modules或Themes>/<扩展ID>/bin&rdquo;中查找名为<扩展ID>.dll的程序集。如果这个文件存在,它将被复制到~/App_Data/Dependencies文件夹下,这是一个特殊的文件夹,在~/Web.config文件中配置进行过配置,是用于ASP.NET查找&ldquo;~/bin&rdquo;文件夹以外程序集的地方。
DynamicExtensionLoader
如果&ldquo;Module.txt&rdquo;文件来自于~/Core、~/Modules、~/Themes 文件夹,DynamicExtensionLoader将在&ldquo;~/<Core、Modules或Themes>/<扩展ID>&rdquo;目录中查找.csproj文件。如果这个文件存在,这个装载机将使用Orchard编译管理器根据.csproj文件来编译程序集并返回改程序集的所有类型&mdash;&mdash;Orchard中扩展的动态编译指的就是这部分了。
RawThemeExtensionLoader
如果&ldquo;Module.txt&rdquo;文件来自于~/Themes 文件夹,则由RawThemeExtensionLoader负责加载。其实基本上不做什么事情,因为如果一个模块有程序集或.csproj文件,就轮不到它,而是交给PrecompiledExtensionLoader或DynamicExtensionLoader来处理了。
二、监视扩展:MonitorExtensions方法
某些操作将导致应用程序重新启动,具体哪些操作可以参考《IIS 5.0 和 6.0 的 ASP.NET 应用程序生命周期概述》。除此之外,其他一些操作也将导致Orchard重启。Orchard使用了一些监视器来进行监视。
MonitorExtensions方法会在Orchard初始化和每次处理BeginRequest事件的时候得于执行。初始化时执行该方法的目的在于处理一种边界情况,那就是在扩展被探测完的时候正进行安装扩展的时候,如果扩展有扩展增删改的操作,能够及时进行某些处理。
首先会监视~/Modules和~/Themes目录的变化(扩展的增删操作),还会监视Module.txt文件修改、扩展源码的修改(将会导致重新动态编译)、扩展引用的第三方程序集(~/<Core、Modules或Themes>/<扩展ID>/bin目录的变动)或扩展引用的其他扩展的变化或改动等。
三、创建和激活Shell:CreateAndActivateShells方法
首先由ShellSettingsManager从~/App_Data/Sitess目录的子目录中搜索Settings.txt文件,并将其反序列化为ShellSettings对象。一系列ShellSettings对象形成一个集合。如果ShellSettings对象集合不为空,则使用ShellContextFactory.CreateShellContext为每个ShellSettings对象创建对应的ShellContext对象,否则使用ShellContextFactory.CreateSetupContext创建一个安装上下文ShellContext对象。
针对具体的ShellContext,调用其包含的Shell(DefaultOrchardShell)的Activate方法进行激活。
另外,会对ShellContext对象集合进行缓存,当需要重新设置扩展时,只需要将其设置null,在下一个BeginRequest时就能够重新启动设置操作。
相关类型:
Orchard.Environment.DefaultOrchardHost
Orchard.Environment.Extensions.ExtensionLoadingContext

Orchard.Environment.Extensions.ExtensionLoaderCoordinator : IExtensionLoaderCoordinator
Orchard.Environment.Extensions.ExtensionManager : IExtensionManager
Orchard.Environment.Extensions.Folders.ExtensionHarvester : IExtensionHarvester
Orchard.Environment.Extensions.Folders.ModuleFolders: IExtensionFolders
Orchard.Environment.Extensions.Folders.CoreModuleFolders: IExtensionFolders
Orchard.Environment.Extensions.Folders.ThemeModuleFolders: IExtensionFolders
Orchard.Environment.Extensions.ExtensionMonitoringCoordinator : IExtensionMonitoringCoordinator
Orchard.Environment.Extensions.Models.ExtensionDescriptor
Orchard.Environment.Extensions.Loaders.ExtensionProbeEntry
Orchard.Environment.Extensions.Loaders.ExtensionReferenceProbeEntry
Orchard.FileSystems.Dependencies.DependencyDescriptor
Orchard.FileSystems.Dependencies.DependencyReferenceDescriptor
Orchard.Environment.Extensions.ExtensionEntry
Orchard.Environment.Configuration.ShellSettings
Orchard.Environment.Configuration.ShellSettingsManager: IShellSettingsManager
Orchard.Environment.ShellBuilders.ShellContext
Orchard.Environment.ShellBuilders.ShellContextFactory: IShellContentFactory
Orchard.Environment.ShellBuilders.ShellContainerFactory
Orchard.Environment.ShellBuilders.CompositionStrategy: ICompositionStrategy
Orchard.Caching.DefaultParallelCacheContext
参考资料:
Orchard module loader and dynamic compilation
Orchard动态编译机制(上文翻译)



作者原创内容不容易,如果觉得内容不错,请点击右侧“打赏”,赏俩给作者花花,也算是对作者付出的肯定,也可以鼓励作者原创更多更好内容。
更多详情欢迎到QQ群 166852192 交流。