在Orchard,模块和主题都是可以插拔式的,在源码处理时,用类型(参考:DefaultExtensionTypes)区分,都没太大的本质区别,以下都称做模块。

插件的支持,实现分以下几步:

  1. 搜集模块的信息
  2. 确定模块的加载器
  3. 复制DLL到App_Data\Dependencies文件夹(动态编译的项目不复制)
  4. 加载启用模块的程序集,如果是动态编译项目,开始编译
  5. 得到程序集的里所有公共的类(不包含IsAbstract)
  6. 加载类型到autofac容器中,构造网站运行环境

搜集模块信息

Orchard目前支持三个目录去搜索模块:Core,Modules,Themes。模块都是存放这几个文件夹下的子文件夹,并且包含Module.txt文件(Core,Modules文件夹下)或Theme.txt文件(Themes下)。

这个搜集外部调用,是接口IExtensionManager函数

 IEnumerable<ExtensionDescriptor> AvailableExtensions();

来实现的。通过接口(IExtensionFolders)的所有实现类来查找。这个过程里,使用了大量Cache(ICacheManager)。

接口(IExtensionFolders)的实现类:

  • CoreModuleFolders:处理Core目录
  • ModuleFolders:处理Modules目录
  • ThemeFolders:处理Themes目录

这些类负责传入要处理的文件夹,处理类型,具体的处理工作由接口(IExtensionHarvester)的实现类(ExtensionHarvester)来完成。以Modules目录为例,该类首先找出Modules下的子目录,循环分析子目录下的Module.txt文件,分析文件后,得到一组ExtensionDescriptor对象。ExtensionDescriptor对象,就是以后处理过程的基础。

确定模块的加载器

Orchard里面有很多的模块,支持的模块加载方式也多样。如Core目录下的模块,代码都放在Orchard.Core.dll中的。Modules目录下的模块,可以支持动态编译,也可以支持加载dll。负责加载模块dll的,就是实现接口(IExtensionLoader)的类。

类名
Order
优先级
说明
CoreExtensionLoader 10 100 处理Core目录下的模块,直接加载Orchard.Core.dll程序集
DynamicExtensionLoader 100 0 如果模块目录下存在文件(模块名.csproj),这个加载器就做为一个备选
PrecompiledExtensionLoader 30 0 如果模块目录下的bin目录存在文件(模块名.dll),这个加载器就做为一个备选
RawThemeExtensionLoader 10 0
处理Themes目录下主题,并且这个主题目录下,不能包含(主题名.csproj)文件和bin目录下,不能包含(主题名.dll)文件。
ReferencedExtensionLoader 20 100 如果模块的程序集已经加载到程序域中,并且在web的bin目录下,存在文件(模块名.dll)这个加载器就做为这个模块的备选项了。这种情况,一般是web项目,直接引用了模块项目。

CoreExtensionLoader

这个加载器只处理Core目录下的,加载程序集时,就加载Orchard.Core.dll,很大程度已经加载到程序域了。也没有其他的备选加载器了。处理相当简单

DynamicExtensionLoader  &  PrecompiledExtensionLoader

这两个加载器,是Modules目录下的模块经常可备选的。我们在开发自己模块时,当我们编译代码后,就生成了文件ModuleName.dll。这个时候,这两种加载器都满足了条件,做为了备选项。这两种加载器,具体使用哪个呢?

  1. 使用优先级最高的
  2. 两个优先级一样时,按依赖项的修改时间,使用依赖项最近修改的。DynamicExtensionLoader 的依赖项是项目文件及其源码,如果还引用了其他项目,那个项目的依赖项,也就包含在内了。PrecompiledExtensionLoader依赖项就是ModuleName.dll文件。 项目的依赖dll是要除去已经加载到程序域的程序集
  3. 依赖项修改时间一样时,使用Order值小的

RawThemeExtensionLoader

这个加就是用来加载不包含项目文件的主题,没有太多的处理

ReferencedExtensionLoader

从表的说明看出,这个加载器,是加载放置在~/bin目录下的模块代码的。为了效率,可以禁用一些加载器,那也就可以把模块代码放置到~/bin目录下了。

复制DLL到App_Data\Dependencies文件夹

确定了模块的加载器后,就需要把一些程序集复制到App_Data\Dependencies目录。从以上的分析可以看出,只有加载器DynamicExtensionLoader 和PrecompiledExtensionLoader需要复制程序集。PrecompiledExtensionLoader需要复制模块dll和依赖项dll。DynamicExtensionLoader 只需要复制依赖项dll就可以了。。经过这一步后,模块的dll及依赖dll,都复制到了Dependencies文件夹。为什么要复制到Dependencies呢?在Orchard.Web项目的web.config的配置节:

runtime>assemblyBinding>probing[privatePath]设置了App_Data/Dependencies。关于这个配置,参考http://msdn.microsoft.com/zh-cn/library/823z9h8w(v=vs.85).aspx。指定加载程序集时公共语言运行库要搜索的应用程序基子目录。就是增加了个类似~/bin目录的程序集搜索目录。

复制完DLL后,会根据可用的模块,模块使用的加载器,在App_Data\Dependencies目录保存两个文件:dependencies.xml和dependencies.compiled.xml文件。这样就保存了处理的结果,不然就白忙活了。

  • dependencies.xml:文件保存模块名,模块目录,加载器名,以及依赖项
  • dependencies.compiled.xml:保存模块的名称,模块目录,加载器名,还有就是模块的Hash值。这个Hash值是通过模块的名称,依赖项,修改时间生成的。

加载启用模块的程序集,如果是动态编译项目,开始编译

模块的信息及DLL都准备好了,下面就是按需加载了。具体加载哪些DLL,是看启用了哪些Feature。启用的Feature保存在文件App_Data目录下的cache.dat文件,它是一个XML文件。这个文件保存多个网站(Shell)启用的Feature。通过模块描述文件(Module.txt),可以知道模块提供了哪些Feature。启用的Feature一对比,就找到了模块名称了。

每个模块使用哪个加载器,在上一步已经确定了。通过对比加载器的名称,找到加载器,调用加载器的函数:

ExtensionEntry LoadWorker(ExtensionDescriptor descriptor);
  • CoreExtensionLoader:直接加载Orchard.Core.dll
  • PrecompiledExtensionLoader:加载模块的程序集
  • ReferencedExtensionLoader:加载~/bin目录下的模块程序集,如果不存在,就返回null
  • RawThemeExtensionLoader:不加载程序集
  • DynamicExtensionLoader:下面形式调用,动态编译程序集。动态编译程序集在此不深入,打算写另一篇文章说明。
    BuildManager.GetCompiledAssembly("~/Modules/ModuleName/ModuleName.csproj");

得到程序集的里所有公共的类

得到程序集后,就会返类型ExtensionEntry

return new ExtensionEntry {
Descriptor = descriptor,
Assembly = assembly,
ExportedTypes = assembly.GetExportedTypes()
};

得到这个对象后,遍历ExtensionEntry.ExportedTypes,根据类型是否有OrchardFeatureAttribute属性,确定类型所属的Feature。最终到的一组Feature类型

public class Feature {
public FeatureDescriptor Descriptor { get; set; }
public IEnumerable<Type> ExportedTypes { get; set; }
}

所有的Feature得到后,首先排除类型中有标注OrchardSuppressDependencyAttribute属性的。这个标注,是为了去掉指定的类型。从类型集中,要得到5种类型:

  • IModule对象:实现接口IModule的类型
  • IDependency对象:实现接口IDependency的类型
  • IController对象:实现接口IController的类型
  • IHttpController对象:实现接口IHttpController的类型
  • Record:1)类型的命名空间以(.Models)或者(.Records)结尾。2)有名称为(Id)的属性,并且是Virtual的。 3)类型不是密封(Sealed) 4)类型不是虚类型(Abstract)5)类型没有实现接口(IContent),如果实现了该接口,但是从ContentPartRecord类型继承的,也可以。

这样就准备好了要加载到autofac容器中的类型。

加载类型到autofac容器中,构造网站运行环境

加载类型到autoface容器,由接口IShellContainerFactory的实现类ShellContainerFactory来处理。

  • 首先从ShellBlueprint.Dependencies集合中找出实现IModule接口的类型,注册到中间容器中。
  • 从中间容器中得到IModule类型,调用RegisterModule注册。
  • 从ShellBlueprint.Dependencies注册类型,并根据实现的接口IDependency类型,设置生命周期

    ISingletonDependency:在一个shell中是单实例的

    IUnitOfWorkDependency:在一个work是单实例的

    ITransientDependency:每次请求都是新的实例

  • 特别注册实现接口IEventHandler的类型,使用接口的名称,命名注册的类型
  • 注册IHttpController类型,添加一个名称为ControllerType,值为类型对象的Metadata,供其他模块使用。
  • 调用IShellContainerRegistrations接口的实现类,使用代码来向容器中注册类型。默认实现什么都不做,可以自己实现这个接口。
  • 从文件注册:支持从两个文件加载 1)~/Config/Sites.config  2)~/Config/Sites.{SiteName}.config 这提供了站点可多样的注册。

最终得到网站的运行环境类型:

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; }
}

属性说明:

  • Setting:网站的配置值,从App_Data\Sites\{SiteName}\Settings.txt读取
  • Descriptor:网站启用的Feature。
  • Blueprint:包含网站关心的类型
  • LifetimeScope:网站独立的容器对象,这样不同的网站对象,就不会影响了
  • Shell:可以启动和终止一个网站。

Orchard源码分析(1):插件式的支持——模块和主题的更多相关文章

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

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

  2. zrender源码分析3--初始化Painter绘图模块

    接上次分析到初始化ZRender的源码,这次关注绘图模块Painter的初始化 入口1:new Painter(dom, this.storage); // zrender.js /** * ZRen ...

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

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

  4. [Abp vNext 源码分析] - 6. DDD 的应用层支持 (应用服务)

    一.简要介绍 ABP vNext 针对于应用服务层,为我们单独设计了一个模块进行实现,即 Volo.Abp.Ddd.Application 模块. PS:最近博主也是在恶补 DDD 相关的知识,这里推 ...

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

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

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

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

  7. Orchard源码分析(6):Shell相关

    概述在Orchard中,提出子站点(Tenant)的概念,目的是为了增加站点密度,即一个应用程序域可以有多个子站点. Shell是子站点(Tenant)级的单例,换句话说Shell代表了子站点.对比来 ...

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

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

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

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

随机推荐

  1. Photoshop:模拟钢笔压力

    预期效果: 方法: 按B选择画笔工具,设置"钢笔压力" 路径描边:选择路径,右击路径,选择“描边路径” 或在路径面板,选择: 即可得到预期效果!

  2. Arcgis Engine最短路径分析

    ArcEngine 最短路径分析(源码)   using System; using ESRI.ArcGIS.Carto; using ESRI.ArcGIS.Geometry; using ESRI ...

  3. WCF入门(五)---创建WCF服务

    使用Microsoft Visual Studio2012创建WCF服务,理解如下所有必要的编码,更好地创建WCF服务的概念,这里做一个简单的任务. 启动Visual Studio 2012. 单击新 ...

  4. 249. Group Shifted Strings

    题目: Given a string, we can "shift" each of its letter to its successive letter, for exampl ...

  5. python 批量更换图片格式脚本

    问题:将某文件下的所有jpg的图片更换为png的图片 简单的实现: # -*- coding:utf-8 -*- from os.path import splitext import glob fr ...

  6. vs2012 arcgis engine 10 丢失arcgis模板

    1.Visual Studio 2012环境下安装ArcGIS Engine 10 Visual Studio 2012环境下安装ArcObject SDK for the Microsoft .Ne ...

  7. 设置COOKIE过期时间的方法

    第一,日期运算法 1)将期限设置为当前日期后的第N天的0时0分0秒 Response.Cookies(LastView).Expires=dateadd(d,N,date) 2)将期限设置为当前日期后 ...

  8. Mysql的 时间戳转换 和 c# 的时间戳转换 (以秒来进行转换,非毫秒,主要是mysql不能存毫秒)

    Mysql 时间戳函数 => 从时间 转成 时间戳   UNIX_TIMESTAMP() 获取当前服务器时间的时间戳 UNIX_TIMESTAMP('2013-01-01 12:33:19') ...

  9. for,foreach,iterator的用法和区别

    for,foreach,iterator的用法和区别 相同点:   三个都可以用来遍历数组和集合不同点:1.形式差别 for的形式是for(int i=0;i<arr.size();i++){. ...

  10. Liunx 配置IDE

    如果你还没装编译环境或自己不确定装没装,不妨先执行 sudo apt-get install build-essential 如果你已经了解一些 vim 的知识,而且想用它来编辑源代码,那么我们不妨装 ...