2012-05-15 5 views
1

안녕하세요, 등록 된 모듈을 사용하는 데이터베이스 애플리케이션이 있습니다.MEF 데이터베이스의 참조 및 설정이있는 플러그인 관리자

모듈은 MEF를 통해 등록되므로이 모듈에 설정을 제공해야합니다 (설정은 데이터베이스에도 저장 됨).

각 플러그인은 다른 인터페이스 (다른 인터페이스를 통해 다른 것을 수행함)를 구현합니다.

현재로서는 각 인터페이스 유형에 대해 별도의 플러그인 컨테이너가 있습니다. 아래에서 작동하지만 각 플러그인 유형마다 많은 복제가 이루어집니다. 모듈 인터페이스

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

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

이하

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

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

모듈과 모듈 설정 테이블은 다음 필드 유형 SQL 데이터베이스에 그때 플러그인 관리자가 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 리터럴을 가질 수 있다고 생각하지 않지만 작업은 수행합니다.

단일 기본 인터페이스 유형을 사용하지 않고 코드보다 유연성이 뛰어나고 코드가 ID와 인터페이스를 인스턴스화 할 때마다 FK를 통해 데이터베이스의 모듈에 연결할 수 있다고 생각합니다. 둘 다 유효한지 확인합니다.

다른 문제 나 개선점이 있으면 의견을 부탁드립니다.

플러그인 관리자

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();