【问题标题】:MEF Plugin manager with references and settings in database具有数据库中的引用和设置的 MEF 插件管理器
【发布时间】:2012-05-15 11:47:50
【问题描述】:

您好,我有一个使用已注册模块的数据库应用程序。

模块是通过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() {}
}

问题是当组成部件的插件管理器类需要一个接口时,我如何处理不同的类型(或者事实上,我们不会提前知道这些接口,因为它们是在运行时加载的)。

非常感谢有关此设计策略的任何方向,包括对数据库模块的引用。

谢谢,

克里斯

【问题讨论】:

    标签: .net plugins interface mef


    【解决方案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();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多