在Orchard,模塊和主題都是可以插拔式的,在源碼處理時,用類型(參考:DefaultExtensionTypes)區分,都沒太大的本質區別,以下都稱做模塊。
插件的支持,實現分以下幾步:
- 搜集模塊的信息
- 確定模塊的加載器
- 復制DLL到App_Data\Dependencies文件夾(動態編譯的項目不復制)
- 加載啟用模塊的程序集,如果是動態編譯項目,開始編譯
- 得到程序集的里所有公共的類(不包含IsAbstract)
- 加載類型到autofac容器中,構造網站運行環境
搜集模塊信息
Orchard目前支持三個目錄去搜索模塊:Core,Modules,Themes。模塊都是存放這幾個文件夾下的子文件夾,並且包含Module.txt文件(Core,Modules文件夾下)或Theme.txt文件(Themes下)。
這個搜集外部調用,是接口IExtensionManager函數
1 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。這個時候,這兩種加載器都滿足了條件,做為了備選項。這兩種加載器,具體使用哪個呢?
- 使用優先級最高的
- 兩個優先級一樣時,按依賴項的修改時間,使用依賴項最近修改的。DynamicExtensionLoader 的依賴項是項目文件及其源碼,如果還引用了其他項目,那個項目的依賴項,也就包含在內了。PrecompiledExtensionLoader依賴項就是ModuleName.dll文件。 項目的依賴dll是要除去已經加載到程序域的程序集
- 依賴項修改時間一樣時,使用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:可以啟動和終止一個網站。