Orchard源碼分析(1):插件式的支持——模塊和主題


在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函數

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。這個時候,這兩種加載器都滿足了條件,做為了備選項。這兩種加載器,具體使用哪個呢?

  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:可以啟動和終止一個網站。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM