WebApi 插件式構建方案:發現並加載程序集

插件式的 WebApi 開發,首要面對的問題就是程序集的發現。由於開發的過程當中,都是在各自的解決方案下進行開發,部署後是分模塊放在一個總體的的運行時網站下。框架

約定

這裏我根據上一節的設定,把插件打包完成後的文件夾,放入網站 bin 目錄下。重複一下這樣作的好處:在插件的配置或者程序集發生變更後,網站會直接從新啓動。編輯器

這是 IIS 的機制,和 WebApi 無關。網站

  • 約定插件的文件夾名稱使用 00_Name 的形式,能夠更方便的按照咱們的要求排列插件。
  • 約定插件的配置文件爲插件根目錄 PluginConfig.xml 文件
  • 約定插件的配置文件以下(後續隨着功能的添加會適當添加內容)
<?xml version="1.0" encoding="UTF-8"?>
  <configuration enabled="true">
    <description>受權支持插件</description>
    <assemblies>
      <add type="relative">bin/Intime.AuthorizationService.dll</add>
      <add type="relative">bin/Intime.AuthorizationService.Services.dll</add>
      <add type="relative">bin/Intime.AuthorizationService.Data.dll</add>
      <add type="relative">bin/Intime.AuthorizationService.Data.Repository.dll</add>
    </assembiles>
</configuration>

解釋一下 XML 配置文檔的含義:ui

  • enabled="true" 表示當前插件的可用性,只有值爲 true 的模塊纔會進行後續操做;不然不作任何操做。
  • description 子元素表示當前模塊的天然語義名稱,在程序上沒有任何含義,面向天然人說明當前模塊的做用。
  • assemblies 子元素表示當前模塊下須要加載的程序集列表。
    • add 元素表示添加一個程序集文件:目前有且只有一個 add 元素受支持。
    • type 表示內含程序集路徑的類型:
      • relative 表示相對路徑,爲程序集文件相對於當前配置文件的路徑:好比第一個文件就是在當前目錄的子目錄 bin 目錄下。
      • absolute 表示絕對路徑,表示程序沒必要作額外工做就能夠根據路徑信息找到文件:此處沒有栗子
    • 內含文本表示程序集的路徑信息。

BuildManager 程序集加載規則

在實現程序集加載以前,咱們有必要大概瞭解一下 BuildManager 類加載程序集的規則。插件

首先,是項目引用,被網站項目引用的 GAC 程序集,都會列入到引用列表中;其次,就是這個類很強大,只要你將程序集放入網站,它就能知道網站運行須要加載這個程序集;最後,還有個不經常使用的設置,Web.config 文件的 runtime 配置節點中引進的目錄,也會被加載。因此咱們要加載的程序集,必須是這三個地方所沒有的程序集。code

另外,就是程序集多版本的問題。這裏咱們約定更新的版本徹底兼容老版本,不然就須要升級代碼以適配這個功能。在網站運行出現多個版本存在的狀況下,咱們約定以下原則:orm

  • bin 目錄是最早加載的路徑,後續插件加載的程序集版本必須小於等於 bin 目錄下程序集的版本。
  • 非 .Net 框架庫自帶的程序集,一概要拷貝到網站 bin 目錄下:請使用 NuGet 管理第三方程序集。
  • 不要使用 Web.config 文件的 runtime 配置節點加載個性目錄。

這樣,咱們能夠定義包含這些元數據的類:配置文件信息、加載的程序集列表,在 PreApplicationStartMethodAttribute 程序集特性設定的方法內,將咱們的程序集加到 BuildManager 管理的 程序集列表中。在程序運行時,.Net 就能夠找到咱們的這些程序集了。xml

部分源代碼:天然語義

代碼是在 Mac 下用文本編輯器寫出來的,請自行腦補。ip

public class DynamicModule
{
  public static DynamicModule Instance
  {
    get{ return Instance == null ? (Instance = new DynamicModule()) : Instance; }
  }
  
  public string BaseDirectory { get; set; }
  
  public ModuleMetadata[] BaseDirectory { get; set; }
  
  ctor()
  {
    BaseDirectory = Path.Combine(App.BaseDirectory, "bin");
    
    var modules = Directory.GetFiles(BaseDirectory, "PlugConfig.xml", AllDirectory)
                           .Where(p => p.Configuration.Enabled);
                           
    bar baseAssemblies = AssemblyName.GetAssemblyNames(BaseDirectory, "*.dll", TopDirectory);
    
    var data = baseAssemblies.Intersect(modules.LoadedAssemblyNames)
                             .Where(p => p.CodeBase != BaseDirectory);
    if(data.Any())
      throw new ModuleConfiguration(string.Format("程序集 {0} 裝載出現異常!", string.Join(data)));
      
    modules.Foreach(p => p.LoadAssemblies());
  }
}
相關文章
相關標籤/搜索