在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. Android getActionBar()报空指针异常

    1. 加载完视图后,再去获取: 写在setContentView()后面. 2.sdk版本: Actionbar的主题在3.0以后才有,使用的时候要确保,最低的版本不能小于3.0. <uses- ...

  2. C/C++技巧

    C中如何调用C++函数 将 C++ 函数声明为``extern "C"''(在你的 C++ 代码里做这个声明),然后调用它(在你的 C 或者 C++ 代码里调用).例如: // C ...

  3. 在Linux中创建静态库和动态库

    我们通常把一些公用函数制作成函数库,供其它程序使用. 函数库分为静态库和动态库两种. 静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库. 动态库在程序编译时并不会被连接到目标代码中 ...

  4. Androidz之Activity概要学习

    Androidz之Activity概要学习 1.     Activity类概述 Activity(活动)是一个单独的.能获取焦点的,且能与用户交互的东西.所以我们通常在Activity类中的onCr ...

  5. Android开发之定义接口暴露数据

    写了一个网络请求的工具类,然后想要获取到网络请求的结果,在网络工具类中写了一个接口,暴露除了请求到的数据 代码: package com.lijingbo.knowweather.utils; imp ...

  6. Android开发之定义app在手机的安装位置

    定义app在手机的安装位置,可以通过在清单文件中添加属性 android:installLocation="" 该属性有三个值:auto(自动),preferExternal(外部 ...

  7. makefile的常用规则

    一.前言 这篇文章介绍在LINUX下进行C语言编程所需要的基础知识.在这篇文章当中,我们将会学到以下内容: 源程序编译 Makefile的编写 程序库的链接 程序的调试 头文件和系统求助 二.正文 1 ...

  8. hdu 4939 Stupid Tower Defense ( dp )

    题目链接 题意:给出一条长为n个单位长度的直线,每通过一个单位长度需要t秒. 有3种塔,红塔可以在当前格子每秒造成x点伤害,绿塔可以在之后的格子每秒造成y点伤害, 蓝塔可以使通过单位长度的时间增加z秒 ...

  9. EASYUI+MVC4通用权限管理平台

    通用权限案例平台在经过几年的实际项目使用,并取得了不错的用户好评.在平台开发完成后,特抽空总结一下平台知识,请各位在以后的时间里,关注博客的更新. 1.EASYUI+MVC4通用权限管理平台--前言 ...

  10. CodeForces 489A (瞎搞) SwapSort

    题意: 给n个整数(可能有重复),输出一个不超过n次交换的方案,使得经过这n次交换后,整个序列正好是非递减的. 分析: 首先说题解给的算法. 从左到右扫一遍,交换第i个数和它后面最小的那个数. 代码看 ...