MEF插件管理器,包含数据库中的引用和设置

时间:2012-05-15 11:47:51

标签: .net plugins interface 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 个答案:

答案 0 :(得分: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();