C#插件架构和对用户可配置数据库设置的引用

时间:2012-10-16 20:04:12

标签: c# .net plugins

我有一个可由用户配置的数据库应用程序 - 其中一些选项是从不同的外部插件系统中选择的。

我有一个基本插件类型,我的数据库架构具有相同字段的插件记录类型。我有一个PlugingMananger在应用程序启动时加载插件(通过IoC容器)并将它们链接到数据库(基本上将磁盘插件中的字段复制到数据库)。

public interface IPlugin
{
    Guid Id{ get; }
    Version Version { get; }
    string Name { get; }
    string Description { get; }
}

然后可以使用PlugingMananger.GetPlugin(Guid pluginId, Guid userId)检索插件,其中用户ID是可能要求插件操作的多个用户之一。

应用程序已经预先声明了一组已知的接口,每个接口都特定于某个功能(格式化程序,外部数据,数据发送器等),如果插件实现了一个未知的服务接口,那么它将被忽略:

public interface IAccountsPlugin : IPlugin
{
    IEnumerable<SyncDto> GetData();
    bool Init();
    bool Shutdown();
}

插件还可以在多用户系统中为每个用户定义设置属性PluginSettingAttribute - 这些属性在为特定用户检索插件时设置,而PluginPropertyAttribute用于常见属性在应用程序启动时注册插件时,插件设置为所有用户和只读设置。

public class ExternalDataConnector : IAccountsPlugin
{
    public IEnumerable<AccountSyncDto> GetData() { return null; }
    public void Init() { }
    public void Shutdown() { }

    private string ExternalSystemUsername;
    // PluginSettingAttribute will create a row in the settings table, settingId
    // will be set to provided constructor parameter. this field will be written to
    // when a plugin is retrieved by the plugin manager with the value for the
    // requesting user that was retrieved from the database.
    [PluginSetting("ExternalSystemUsernameSettingName")]
    public string ExternalSystemUsername
    {
        get { return ExternalSystemUsername }
        set { ExternalSystemUsername = value; } 
    }

    // PluginPropertyAttribute will create a row in the attributes table common for all users
    [PluginProperty("ShortCodeName")]
    public string ShortCode
    {
        get { return "externaldata"; }
    }

    public Version PluginVersion
    {
        get { return new Version(1, 0, 0, 0); }
    }

    public string PluginName
    {
        get { return "Data connector"; }
    }

    public string PluginDescription
    {
        get { return "Connector for collecting data"; }
    }
}

以下是我在寻求指导的问题和方面:

  1. 通过上述将IoC容器中的插件链接到数据库的抽象,用户可以选择数据库字段Customer.ExternalAccountsPlugin = idOfExternalPlugin。这感觉很重 - 有没有其他系统实现这一点的简单方法(例如,SharePoint有很多用户数据库引用的插件)?

  2. 我的应用程序在编译时指示它支持的接口并忽略所有其他接口 - 我已经看到一些系统声称可以使用开放插件完全扩展,我认为这意味着许多松散类型的接口和转换,是否存在两个选项之间的中间位置,允许在不重新编译但仍使用具体接口的情况下发布未来的更新?

  3. 我的插件可能包含元数据(PluginProperty或PluginSetting),我不确定存储它的最佳位置,无论是在插件元数据表中(会使linq查询更复杂)还是直接在插件数据库记录行中( easy linq查询PluginManager.GetPluginsOfType<IAccounts>.Where(x => x.ShortCode = "externaldata").FirstOrDefault();,这是最佳做法吗?

  4. 由于插件功能和接口在很大程度上依赖于数据库模式,我建议的限制插件使用特定模式修订的方法是什么?我会将此架构修订保留为数据库中设置表中的单行,并在每次发布后手动更新吗?插件是否支持最大架构版本,或者应用程序是否支持已知插件版本列表?

1 个答案:

答案 0 :(得分:3)

1)对不起,但我不确定。但是,我很确定,在自定义插件创建或处理数据的软件中,他们按照您描述的方式处理插件。想法是,如果用户加载数据但缺少该特定插件,则数据不会被破坏,并且不允许用户修改该数据。 (我想到的一个例子是3D软件)

2)当然,只提供非常严格的接口实现会严重限制插件的创建。 (例如:Excel,我不能创建一个新的细胞类型)它不坏或不好,它高度取决于你想要它,它是一个选择。如果您希望插件创建者仅通过某些非常具体的管道访问数据,则限制他可以创建的数据类型,然后它与您的设计一致。否则,如果您的目标是打开您的软件以进行改进,那么您还应该公开一些您认为足够安全以便在外部使用的类和方法。 (例如:Maya,我可以创建一个派生自基类的新实体类型,而不仅仅是一个接口)

3)嗯,这取决于很多事情,不是吗?序列化数据时,您可以创建一个包装器,其中包含特定插件,ID,MetaData以及您需要判断的任何其他信息。我会这样做,因为它更容易检索,但它是你需要的最佳方式吗?没有更多信息就很难说。

4)一个很好的例子就是Firefox。较小的版本增量不会改变插件兼容性。考虑到它实现的内容,如果插件仍然有效,则从数据库进行中等版本增量测试。如果插件没有实现更改的内容,它仍然有效。主要版本增量需要重新编译所有插件才能使用新定义。从我的观点来看,它是一个很好的中间地带,允许开发人员不总是重新编译,但它使主要软件的开发稍微棘手,因为必须提前计划更改。这个想法是平衡软件开发者和插件开发者之间的PitA(屁股中的痛苦)因素。

嗯......这是我2美分的长期收藏。