Orchard作为一个可扩展的CMS系统,需要在运行时加载一些模块或主题(统称扩展)。从Orchard 0.5版起就致力于让安装和升级这些扩展变得更加简单。Orchard和其他任何一个ASP.NET MVC应用程序一样,都可以在Visual Studio环境下编译。但是Orchard还提供了另外一种模块加载策略,例如,它允许模块的dll无需部署在网站的bin目录下。此外Orchard还可以动态的根据模块源代码来编译模块。这样可以比较灵活的部署dll文件,并且可支持在没有Visual Studio环境的情况下随时编译所修改的模块源代码。这有点类似于ASP.NET的“App_Code”文件夹,只不过Orchard支持更多的这样的文件夹。本文将从技术层面上描述Orchard是如何加载模块并动态编译的。
概述
Orchard应用程序启动时,Orchard框架(具体由ExtensionLoaderCoordinator类负责)需要明白安装了那些模块并激活他们(就是加载这些模块的dll)。更细化的来说这个过程分为三个阶段:
1、发现:搞清楚目前网站都有哪些模块。
2、激活:搞清楚用什么策略来激活或加载每一个模块。
3、引用解析:搞清楚激活这些模块都需要引用那些程序集。这个阶段技术属于“激活”阶段的一部分,但它作为一个单独的部分更容易去思考关于引用解析的问题。
一旦模块被正取激活,它们将在进一步检查去检测并启用个别的功能,不过这个就不是本文需要讨论的主题了。
发现
列出Orchard中可用的模块是通过查询一些文件夹下面的“module.txt”文件获得的。下面将分别列举这些默认查询的文件夹:
"~/Modules" 文件夹
"~/Modules" 文件夹包含了大多数Orchard模块。Orchard约定每一个模块存储在一个名为<ModuleName>的子文件夹中,并且需要包含一个“module.txt”文件。打包、发布和分析模块只此次文件夹下的模块。这个文件夹就是用户自定义扩展Orchard模块的文件夹。
"~/Core"文件夹
"~/Core"文件夹包含一些Orchard中最核心的模块,这些模块有Orchard团队提供。用户通常不在这里添加新的模块。
"~/Themes"文件夹
"~/Themes"文件夹包含了Orchard主题。作为动态编译,主题几乎被当作模块一样处理,处理主题没有代码(bin 和 .csproj文件)。本文的其余部分,当我们提到“模块”时,他同样也适用于“主题”。
注:”~/”表示网站根目录。
示例
下面是一个Orchard安装实例,其中包含6个扩展:两个核心模块“Common”和“Localization”,两个应用模块“Foo”和“Bar”,还有两个主题“T1”和“T2”。
RootFolder Core Common module.txt <= "Common" module from "Core" Localization module.txt <= "Localization" module from "Core" Modules Foo module.txt <= "Foo" module Bar module.txt <= "Bar" module Themes T2 theme.txt <= "T1" theme T2 theme.txt <= "T2" theme
激活
一旦Orchard从发现阶段收集了所有的“module.txt”文件,Orchard将使用不同的策略(或“模块装载机”)在内存中加载这些模块。在内部,加载模块的行为,是一种活动。作为输入“module.txt”文件并返回一个“System.Type”列表。请注意,这有别于返回一个“System.Assembly”列表,因为Orchard支持一个程序集包含多个模块,如:“Orchard.Core.dll”就包含了约10个Orchard模块。Orchard目前实现了以下模块加载策略(装载机):
“已引用模块”装载机
这个装载机在“~/bin”目录中查找“module.txt”文件中模块名对应的程序集,如果这个程序集存在,它将加载并返回改程序集的所有类型。这种加载机用于当所有模块都是预先编译好的,并且其dll都存储在“~/bin”目录中的情况,这是一种典型“asp.net mvc”应用程序方式。
“核心模块”装载机
如果“module.txt”文件来自于"~/Core"文件夹,核心模块装载机将返回来自于Orchard.Core.dll中,"Orchard.Core.<moduleMame>"命名空间下的所有类型。Orchard.Core.dll是一个特殊的程序集,它包含了Orchard核心模块,在Orchard框架基础上提供一些基本功能。
“预编译模块”装载机
如果“module.txt”文件来自于"~/Modules" 文件夹,预编译模块装载机将在“~/Modules/<ModuleName>/bin”中查找名为<moduleMame>的程序集。如果这个文件存在,它将被复制到"~/App_Data/Dependencies"文件夹下,这是一个特殊的文件夹,是用于ASP.net查找“~/bin”文件夹以外程序集的地方。
“动态模块”装载机
如果“module.txt”文件来自于"~/Modules" 文件夹,动态模块装载机将在“~/Modules/<ModuleName>/bin”中查找.csproj文件。如果这个文件存在,这个装载机将使用Orchard编译管理器根据.csproj文件来编译程序集并返回改程序集的所有类型。
注:这个装载机在系统执行的时候只有一个,常常被称为“动态编译”。事实上这是可以选择的,如果所有模块都是预编译好的,也可以选择其他的装载机。
装载机选择
既然可能有不止一个装载机能够加载一个指定的模块,Orchard是如何做到选择正确的呢?每个装载机都可以返回一个相应模块的 “最后修改时间”,对于存在多个装载机时,Orhcard根据最新的“最后修改时间”来决定使用何种装载机。例如:一个模块包含所有的源文件包括.csporj文件和已经编译到bin目录中的dll。第一次加载模块的时候,Orchard会在bin目录中加载程序集,应为源代码比程序集新的情况会比较少。然而,如果源代码有了任何改变后,动态模块装载机返回的“最后修改时间”大于在bin目录中加载程序集的“最后修改时间”,Orchard将采用动态模块装载机。值得注意的是这种分歧只存在于"~/Modules" 文件夹下的模块,因为"~/Core"文件夹下的模块只会采用核心模块装载机。
示例
RootFolder Bin Orchard.Web.dll Orchard.Core.dll Foo.dll Core Common <= "Core Module" loader module.txt Localization <= "Core Module" loader module.txt Modules Foo <= "Reference Module" loader (because a "~/bin/Foo.dll" file exists) module.txt Bar <= "Precompiled Module" loader (because a "~/Modules/Bar/bin/Bar.dll" file exists) bin Bar.dll module.txt Baz <= "Dynamic Module" loader (because a "~/Modules/Baz/Baz.csproj" file exists) Controller BazControler.cs Baz.csproj module.txt
引用解析
动态模块加载机将根据.csproj文件中References节点的内容在模块的Bin目录中查找模块所引用的其他程序集文件,并将其复制到"~/App_Data/Dependencies"文件夹下。注意模块所引用的其他dll需要复制到模块Bin目录下,并起引用位置也要在此目录中。
配置更改检测
正如上文所述,在应用程序启动时加载模块。然而,一旦应用程序启动,改变依然可能随时发生,如:一个模块的源代码可能手动更新,一个模块可能会在网站上安装或删除等。来检测这些变化,就需要Orchard提供一个“监视”潜在变化的机制并在发生变化是通知模块装载器。当检测到变化时,当前模块的配置将被丢弃并且将同时重新审查、加载和激活模块,就像应用程序重新启动一样。在某些情况下,这些变化需要一个ASP.net的AppDomain重新启动(如:一个新版本模块的程序集需要被加载),Orchard将检测这样的情况并让一个ASP.net AppDomain重新启动。
"~/App_Data/Dependencies/Dependencies.xml" 文件
这个文件包含了最后一次成功加载应用程序时的模块、装载机和模块所引用的程序集列表。查看这个文件的内容,可以用于调试,检测相应的dll是否被加载。
参考文档
http://www.orchardproject.net/docs/Orchard-module-loader-and-dynamic-compilation.ashx
==========================================
作者:二十四画生
转载请注明来源于博客园——二十四画生的Blog,并保留有原文链接。