2012-05-15 49 views
1

嗨,我有一個使用註冊模塊的數據庫應用程序。MEF插件管理器與數據庫中的參考和設置

模塊通過MEF註冊,我需要爲這些模塊提供設置(其設置也存儲在數據庫中)。

每個插件將實現不同的接口(通過不同的接口做不同的事情)。

截至目前,我目前有一個單獨的插件容器爲每個接口類型 - 下面的工作,有很多複製爲每個插件類型。

的模塊和模塊設置表與以下字段類型SQL數據庫

[Table_PluginModules] 
PluginId => Guid, PK 
Enabled => boolean 

[Table_PluginModule_settings] 
PluginId => Guid, PK(col1), FK(Table_PluginModules.PluginID) 
SettingName => varchar, PK(col2) 
SettingValue => varchar 

模塊接口低於

interface ICar 
{ 
    string Name; 
    Guid PluginID; 
    void Handbrake(); 
} 

interface IBike 
{ 
    string Name; 
    Guid PluginID; 
    void Peddle(); 
} 

然後我有插件管理器對於ICar和IBike接口

class CarPluginsManager() 
{ 
    [ImportMany] 
    public ICar[] Plugins 

    void LoadPluginsCar() 
    { 
    var cat = new DirectoryCatalog(path, "*.dll"); 
    var container = new CompositionContainer(cat); 
    container.ComposeParts(this); 
    foreach (ICar plugin in Plugins) 
    { 
     // load settings from database for this plugin 
     List<ModuleSetting> settings = ModelContext.PluginSettings(x => x.PluginId == plugin.Plugin).ToList(); 
     foreach(ModuleSetting setting in settings) 
     { 
      // do something with the setting value that was retreived for this plugin 
      string settingName = setting.SettingName; 
      string settingValue = setting.SettingValue; 
     } 
       // check if previously registered modules are not in the above list and disable if necesasry in the database 
    } 
    } 

    ICar GetCarPlugin(Guid id) 
    { 
    foreach (var plugin in Plugins) 
    { 
     if(plugin.PluginID == id) 
       { 
          // check module is enabled in the modules database table, if so 
      return plugin; 
          // if module is disabled, return null... 
       } 
    } 
    return null; 
    } 

} 

class BikePluginsManager() 
{ 
    [ImportMany] 
    public IBike[] Plugins 

    void LoadPluginsCar() 
    { 
    var cat = new DirectoryCatalog(path, "*.dll"); 
    var container = new CompositionContainer(cat); 
    container.ComposeParts(this); 
    foreach (ICar plugin in Plugins) 
    { 
     // load settings from database for this plugin 
     List<ModuleSetting> settings = ModelContext.PluginSettings(x => x.PluginId == plugin.Plugin).ToList(); 
     foreach(ModuleSetting setting in settings) 
     { 
      // do something with the setting value that was retreived for this plugin 
      string settingName = setting.SettingName; 
      string settingValue = setting.SettingValue; 
     } 
       // check if previously registered modules are not in the above list and disable if necesasry in the database 
    } 
    } 

    IBike GetBikePlugin(Guid id) 
    { 
    foreach (var plugin in Plugins) 
    { 
     if(plugin.PluginID == id) 

          // check module is enabled in the modules database table, if so 
      return plugin; 
          // if module is disabled, return null... 
    } 
    return null; 
    } 
} 

業務邏輯代碼的其他方面將引用不同的模塊來做不同的事情。

當模塊被加載時,我檢查是否有任何先前註冊的模塊丟失(如果DLL已從插件目錄中刪除)並禁用插件表中的插件。

這些模塊需要在數據庫中「註冊」,以便我們可以將這些模塊作爲外鍵引用(用戶可以選擇不同的輸出模塊),我目前通過一個名爲plugins的表(在此頂部定義後)和示例動作見下表:

[Table_Some_Action] 
ActionID => int, PK 
ModuleID => Guid, FK(Table_Modules) 

所以,我想使這個普通/通用的,但是我仍然需要能夠通過接口類型引用接口。我在想什麼的做線沿線的東西:

interface IPlugin 
{ 
    string Name; 
    Guid PluginID; 
} 

IPlugInterfaceCar : IPlugin 
{ 
void ApplyHandbrake(); 
} 

IPlugInterfaceBike : IPlugin 
{ 
void Peddle(); 
} 

class CarPluginBinary : IPlugInterfaceCar 
{ 
    void ApplyHandbrake() {} 
} 

class BikePluginBinary : IPlugInterfaceBike 
{ 
    void ApplyHandbrake() {} 
} 

的問題是如何處理不同類型時組成部分的插件管理器類只需要一個接口(或逸岸,我們不會知道這些接口提前,因爲它們是在運行時加載的)。

本設計策略的任何方向,包括對數據庫模塊的引用,都將非常感激。

謝謝,

克里斯

回答

1

我已經寫以下模塊和鏈接插件模塊作爲一個屬性。不幸的是,這是一個字符串,因爲我不認爲它可能有GUID文字,但是它完成了這項工作。

我認爲它比以前的代碼更靈活,不必訴諸於單一的基本接口類型,現在我可以通過FK鏈接到數據庫中的模塊,只要代碼將實例化模塊的ID和接口進行檢查以確保它們都是有效的。

如果您發現任何其他問題或改進,您可以隨時發表評論。

的插件管理器

public interface IPluginManagerService 
{ 
    void RegisterModules(); 
    IExternalAccountsPlugin GetExternalAccountsPlugin(Guid id); 
    IRecoveryActionPlugin GetExternalRecoveryActionsPlugin(Guid id); 
} 

namespace MyNamespace.Services 
{ 
    public class PluginManagerService : ServiceBase, IPluginManagerService 
    { 
     public PluginManagerService(ILogger logger, IUnitOfWork unitOfWork) 
      : base(logger) 
     { 
      m_UnitOfWork = unitOfWork; 
      RegisterModules(); 
     } 

     protected IUnitOfWork m_UnitOfWork; 
     object Lock = new object(); 

     // do not make public so we can perform further checks via strongly typed accessor functions 
     // get a list of modules that implement contracts of type T 
     protected IEnumerable< Lazy<T, IPluginMetadata> > GetInterfaces<T>() 
     { 
      return m_Container.GetExports<T, IPluginMetadata>(); 
     } 

     // do not make public so we can perform further checks via strongly typed accessor functions 
     // returns the plugin with the provided ID 
     protected T GetPlugin<T>(Guid id) 
     { 
      return m_Container.GetExports<T, IPluginMetadata>().Where(x => x.Metadata.PluginID == id.ToString().ToUpper()).Select(x => x.Value).FirstOrDefault(); 
     } 

     public IExternalAccountsPlugin GetExternalAccountsPlugin(Guid id) 
     { 
      return GetPluginModule<IExternalAccountsPlugin>(id); 
     } 

     public IRecoveryActionPlugin GetExternalRecoveryActionsPlugin(Guid id) 
     { 
      return GetPluginModule<IRecoveryActionPlugin>(id); 
     } 

     /* return a list of all available externalAccounts plugins */ 
     public IEnumerable<Guid> ListExternalAccountsPluginIDs() 
     { 
      List<Guid> guids = new List<Guid>(); 

      foreach (string id in GetInterfaces<IExternalAccountsPlugin>().Select(x => x.Metadata.PluginID).ToList()) 
      { 
       guids.Add(Guid.Parse(id)); 
      } 
      return guids; 
     } 

     protected T GetPluginModule<T>(Guid id) 
     { 
      ExternalPlugin pluginInDb = m_UnitOfWork.ExternalPlugins.GetByID(id); 
      if (pluginInDb != null) 
      { 
       if (pluginInDb.Enabled == true) 
       { 
        T binaryPlugin = GetPlugin<T>(id); 
        if (binaryPlugin == null) 
         throw new KeyNotFoundException(); 
        else 
         return binaryPlugin; 
       } 
      } 
      return default(T); 
     } 

     CompositionContainer m_Container; 

     public void RegisterModules() 
     { 
      lock (Lock) 
      { 
       var pluginContainer = new AggregateCatalog(); 
       var directoryPath = Path.GetDirectoryName(Assembly.GetCallingAssembly().Location) + "\\Plugins\\"; 
       var directoryCatalog = new DirectoryCatalog(directoryPath, "*.dll"); 
       //pluginContainer.Dispose(); 
       // directoryCatalog.Refresh(); 

       LogMessage("Searching for modules in " + directoryCatalog + "..."); 

       pluginContainer.Catalogs.Add(directoryCatalog); 
       m_Container = new CompositionContainer(pluginContainer); 

       // m_Container.ComposeParts(this); // not required, this will load dependencies dynamically onto our object 
       LinkModulesWithDatabase(); 
      } 
     } 

     string FormatModuleMessage(string name, Guid id, Version version) 
     { 
      return name + ". ID: " + id + ". Version: " + version.ToString(); 
     } 

     protected delegate string ModuleNameHelper<T>(T t); 
     protected delegate Version ModuleVersionHelper<T>(T t); 

     protected void SetVersionOfExternalPlugin(ref ExternalPlugin plugin, int major, int minor, int build, int revision) 
     { 
      plugin.MajorVersion = major; 
      plugin.MinorVersion = minor; 
      plugin.Build = build; 
      plugin.RevsionVersion = revision; 
     } 

     protected void LoadNewAndExisting<T>(IEnumerable<Lazy<T,IPluginMetadata>> foundModules, ref int added, ref int disabled, ref int upgraded, ref int existing, ModuleNameHelper<T> moduleNameHelper, ModuleVersionHelper<T> moduleVersionHelper) 
     { 
      List<Guid> foundModuleIDs = new List<Guid>(); 

      foreach (Lazy<T,IPluginMetadata> moduleInAssembly in foundModules) 
      { 
       Guid moduleInAssemblyId = Guid.Parse(moduleInAssembly.Metadata.PluginID); 
       Version moduleInAssemblyVersion = moduleVersionHelper(moduleInAssembly.Value); 
       string moduleInAssemblyName = moduleNameHelper(moduleInAssembly.Value); 

       ExternalPlugin moduleInDb = m_UnitOfWork.ExternalPlugins.GetByID(moduleInAssemblyId); // see if we can find the registered module in the database 

       if (moduleInDb != null) 
       { 
        Version moduleInDbVersion = new Version(moduleInDb.MajorVersion, moduleInDb.MinorVersion, moduleInDb.Build, moduleInDb.RevsionVersion); 

        if (moduleInAssemblyVersion > moduleInDbVersion) 
        { 
         LogMessage("Found updated module (previous version " + moduleInDbVersion + "). Upgrading " + FormatModuleMessage(moduleInAssemblyName, moduleInAssemblyId, moduleInAssemblyVersion)); 
         moduleInDb.Enabled = true; 
         SetVersionOfExternalPlugin(ref moduleInDb, moduleInAssemblyVersion.Major, moduleInAssemblyVersion.Minor, moduleInAssemblyVersion.Build, moduleInAssemblyVersion.Revision); 
         upgraded++; 
        } 
        else if (moduleInAssemblyVersion < moduleInDbVersion) 
        { 
         LogMessage("Found old module (expected version " + moduleInDbVersion +"). Disabling " + FormatModuleMessage(moduleInAssemblyName, moduleInAssemblyId, moduleInAssemblyVersion)); 
         moduleInDb.Enabled = false; 
         disabled++; 
        } 
        else 
        { 
         LogMessage("Loaded existing module " + FormatModuleMessage(moduleInAssemblyName, moduleInAssemblyId, moduleInAssemblyVersion)); 
         moduleInDb.Enabled = true; 
         existing++; 
        } 
        moduleInDb.UpdatedDateUTC = DateTime.Now; 
        m_UnitOfWork.ExternalPlugins.Update(moduleInDb); 
       } 
       else // could not find any module with the provided ID 
       { 
        ExternalPlugin newModule = new ExternalPlugin(); 
        SetVersionOfExternalPlugin(ref newModule, moduleInAssemblyVersion.Major, moduleInAssemblyVersion.Minor, moduleInAssemblyVersion.Build, moduleInAssemblyVersion.Revision); 
        newModule.Enabled = true; 
        newModule.ExternalPluginID = moduleInAssemblyId; 
        newModule.UpdatedDateUTC = DateTime.UtcNow; 
        m_UnitOfWork.ExternalPlugins.Insert(newModule); 
        LogMessage("Loaded new module " + FormatModuleMessage(moduleInAssemblyName, moduleInAssemblyId, moduleInAssemblyVersion)); 
        added++; 
       } 
       foundModuleIDs.Add(moduleInAssemblyId); 
      } 

      IEnumerable<Guid> missingModules = m_UnitOfWork.ExternalPlugins.Context.Select(x => x.ExternalPluginID).Except(foundModuleIDs).ToList(); 
      foreach(Guid missingID in missingModules) 
      { 
       ExternalPlugin pluginToDisable = m_UnitOfWork.ExternalPlugins.GetByID(missingID); 
       LogMessage("Cannot find previously registered module in plugin directory. Disabling: " + FormatModuleMessage("Unknown", pluginToDisable.ExternalPluginID, new Version(pluginToDisable.MajorVersion, pluginToDisable.MinorVersion, pluginToDisable.RevsionVersion, pluginToDisable.Build))); 
       pluginToDisable.Enabled = false; 
       pluginToDisable.UpdatedDateUTC = DateTime.UtcNow; 
       m_UnitOfWork.ExternalPlugins.Update(pluginToDisable); 
       disabled++; 
      } 

      m_UnitOfWork.Save(); 
     } 

     protected void LinkModulesWithDatabase() 
     { 
      int added = 0; 
      int existing = 0; 
      int upgraded = 0; 
      int disabled = 0; 

      LogMessage("Loading ExternalAccountsModule plugins (IExternalAccountsModule)."); 
      ModuleNameHelper<IExternalAccountsPlugin> accountsModuleNameHelper = delegate (IExternalAccountsPlugin t) { return t.Name; }; 
      ModuleVersionHelper<IExternalAccountsPlugin> accountsModuleVersionHelper = delegate (IExternalAccountsPlugin t) { return t.ModuleVersion; }; 
      IEnumerable<Lazy<IExternalAccountsPlugin, IPluginMetadata>> accountsPlugins = GetInterfaces<IExternalAccountsPlugin>(); 
      LoadNewAndExisting<IExternalAccountsPlugin>(accountsPlugins, ref added, ref disabled, ref upgraded, ref existing, accountsModuleNameHelper, accountsModuleVersionHelper); 

      LogMessage("Finished loading modules, total " + (added + existing + upgraded) + " modules enabled. New: " + added + " . Existing: " + existing + ". Upgraded: " + upgraded + ". Disabled: " + disabled + "."); 
     } 
    } 
} 

導出屬性

public interface IPluginMetadata 
{ 
    string PluginID { get; } 
} 

[MetadataAttribute] 
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] 
public class PluginExportAttribute : ExportAttribute, IPluginMetadata 
{ 
    public PluginExportAttribute(Type t, string guid) 
     : base(t) 
    { 
     PluginID = guid.ToUpper(); 
    } 

    public string PluginID { get; set; } 
} 

標記模塊作爲一個插件,把這個編譯的DLL中的插件目錄

[PluginExport(typeof(IExternalAccountsPlugin),"BE112EA1-1AA1-4B92-934A-9EA8B90D622C")] 
public class MyModule: IExternalAccountsPlugin 
{ 
} 

IPluginManagerService externalAccountsModuleService = instance.Resolve<IPluginManagerService>(); 
IExternalAccountsPlugin accountsPlugin = externalAccountsModuleService.GetExternalAccountsPlugin(Guid.Parse("BE112EA1-1AA1-4B92-934A-9EA8B90D622C")); 
IEnumerable<Guid> pluginIDs = externalAccountsModuleService.ListExternalAccountsPluginIDs();