您好我有一个使用注册模块的数据库应用程序。
模块通过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() {}
}
问题是当组成部件的插件管理器类需要单个接口时(或者事实上,我们不会提前知道这些接口,因为它们是在运行时加载的),我如何处理不同的类型。
非常感谢此设计策略的任何指导,包括对数据库中模块的引用。
谢谢,
克里斯
答案 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();