C#插件架构,接口在插件之间共享

我把我的问题分成了一个简短的长版本,适合那些没有时间的人。

精简版:

我需要一些带有提供者和消费者插件的系统架构。 提供商应实施interface IProvider,消费者应实施IConsumer。 执行的应用程序应该只知道IProvider和IConsumer。 消费者实现可以向执行程序集(通过ServiceProcessor)询问哪些提供程序实现InterfaceX并获取List。 这些IProvider对象应该被转换为InterfaceX(在使用者中),以便能够将使用者挂钩到InterfaceX定义的某些事件上。 这将失败,因为执行程序集以某种方式不知道此InterfaceX类型(强制转换失败)。 解决方案是将InterfaceX包含在插件和执行程序集引用的某个程序集中,但这应该意味着对每个新的提供者/消费者对进行重新编译并且非常不受欢迎。

有什么建议?

长版:

我正在开发某种通用服务,它将使用插件来实现更高级别的可重用性。 该服务包含使用提供者和使用者的某种Observer模式实现。 提供者和使用者都应该是主应用程序的插件。 让我首先通过列出我在解决方案中的项目来解释服务是如何工作的。

项目A:用于托管所有插件和基本function的Windows服务项目。 TestGUI Windows窗体项目用于更轻松的调试。 来自Project B的ServiceProcessor类的一个实例正在执行与插件相关的东西。 此项目的子文件夹“Consumers”和“Providers”包含子文件夹,其中每个子文件夹分别包含一个使用者或提供者插件。

项目B:包含ServiceProcessor类的类库(在插件之间执行所有插件加载和调度等),IConsumer和IProvider。

项目C:链接到项目B的类库,由TestConsumer(实现IConsumer)和TestProvider(实现IProvider)组成。 TestProvider实现了一个额外的接口(ITest,它本身派生自IProvider)。

这里的目标是Consumer插件可以向ServiceProcessor询问它所拥有的提供者(至少实现IProvider)。 返回的IProvider对象应该被转换为它在IConsumer实现中实现的其他接口(ITest),以便使用者可以将事件处理程序挂钩到ITest事件。

项目A启动时,将加载包含使用者和提供者插件的子文件夹。 以下是我到目前为止遇到的一些问题并试图解决。

ITest过去常常驻留在Project C中,因为这仅适用于TestProvider和TestConsumer所知道的方法和事件。 一般的想法是保持项目A简单并且不知道插件彼此做什么。

使用项目C中的ITest和TestConsumer的Initialize方法中的代码将IProvider转换为ITest(当实现ITest的对象被称为IConsumer对象时,这不会在单个类库中失败)会发生无效的转换错误。 通过将ITest接口放入项目A引用的项目B中,可以解决此错误。 这是非常不受欢迎的,因为我们需要在构建新接口时重新编译项目A.

我试图将ITest放在由项目C引用的单个类库中,因为只有提供者和使用者需要知道这个接口,但没有成功:当加载插件时,CLR声明无法找到引用的项目。 这可以通过挂钩当前AppDomain的AssemblyResolve事件来解决,但不知何故这似乎也是不需要的。 ITest再次回到了B项目。

我尝试将项目C拆分为消费者和提供者的单独项目,并且都加载本身运行良好的程序集:两个程序集都驻留在Assemblies集合中或当前的AppDomain:Assembly found:Datamex.Projects.Polaris.Testing.Providers ,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = 2813de212e2efcd3发现大会:Datamex.Projects.Polaris.Testing.Consumers,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = ea5901de8cdcb258

由于消费者使用提供者,因此从消费者到提供者进行了引用。 现在AssemblyResolve事件再次触发,表明它需要以下文件:AssemblyName = Datamex.Projects.Polaris.Testing.Providers,Version = 1.0.0.0,Culture = neutral,PublicKeyToken = 2813de212e2efcd3

我的问题:这是为什么? 这个文件已经加载了吗? 为什么从IProvider转换到某个界面我知道它实现了不可能? 这可能是因为执行程序本身不知道这个接口,但不能动态加载?

我的最终目标:消费者插件询问ServiceProcessor哪些提供商实现了接口x。 提供程序可以转换为此接口x,而无需执行程序集识别接口x。

有人可以帮忙吗?

在此先感谢,Erik

我只是尝试尽可能地重新创建您的解决方案,而且我没有这样的问题。 (警告,许多代码示例如下……)

第一个项目是应用程序,它包含一个类:

public class PluginLoader : ILoader { private List _providers = new List(); public PluginLoader() { LoadProviders(); LoadConsumers(); } public IProvider RequestProvider(Type providerType) { foreach(Type t in _providers) { if (t.GetInterfaces().Contains(providerType)) { return (IProvider)Activator.CreateInstance(t); } } return null; } private void LoadProviders() { DirectoryInfo di = new DirectoryInfo(PluginSearchPath); FileInfo[] assemblies = di.GetFiles("*.dll"); foreach (FileInfo assembly in assemblies) { Assembly a = Assembly.LoadFrom(assembly.FullName); foreach (Type type in a.GetTypes()) { if (type.GetInterfaces().Contains(typeof(IProvider))) { _providers.Add(type); } } } } private void LoadConsumers() { DirectoryInfo di = new DirectoryInfo(PluginSearchPath); FileInfo[] assemblies = di.GetFiles("*.dll"); foreach (FileInfo assembly in assemblies) { Assembly a = Assembly.LoadFrom(assembly.FullName); foreach (Type type in a.GetTypes()) { if (type.GetInterfaces().Contains(typeof(IConsumer))) { IConsumer consumer = (IConsumer)Activator.CreateInstance(type); consumer.Initialize(this); } } } } 

显然,这可以大大整理。

下一个项目是共享库,它包含以下三个接口:

 public interface ILoader { IProvider RequestProvider(Type providerType); } public interface IConsumer { void Initialize(ILoader loader); } public interface IProvider { } 

最后是包含这些类的插件项目:

 public interface ITest : IProvider { } public class TestConsumer : IConsumer { public void Initialize(ILoader loader) { ITest tester = (ITest)loader.RequestProvider(typeof (ITest)); } } public class TestProvider : ITest { } 

应用程序和插件项目都引用共享项目,插件dll被复制到应用程序的搜索目录中 – 但它们不会相互引用。

当构造PluginLoader时,它会找到所有的IProviders然后创建所有IConsumers并在它们上调用Initialize。 在初始化内部,消费者可以从加载器请求提供者,并且在该代码的情况下,构造并返回TestProvider。 所有这些对我来说都是有效的,没有花哨的控制assembly的装载。

它仍在开发中,但听起来像MEF的完美用例(包含在.Net 4中)并在VS2010内部使用。

MEF为运行时可扩展性问题提供了一个简单的解决方案。 到目前为止,任何想要支持插件模型的应用程序都需要从头开始创建自己的基础架构。 这些插件通常是特定于应用程序的,并且不能在多个实现中重用。

预览已在http://www.codeplex.com/MEF上提供

Glen Block的博客也很有用。

您可能会发现我的文章对于查看插件框架的工作示例很有用,以及如何通过创建包含接口的公共程序集来解决这些问题:

C#Basic教程中的插件:

http://www.codeproject.com/KB/cs/pluginsincsharp.aspx

后续文章,启用了Generics插件管理器库:

http://www.codeproject.com/KB/cs/ExtensionManagerLibrary.aspx

如果您有疑问,两个不相关的程序集如何共享同一个接口,答案是“您不能”解决方案是在所有程序集中包含接口,也许在插件构建器可以引用的dll中,以及在您的装载组件。

我已经做了类似于你想要做的事情,只要我在自动装载机看到的地方assembly,我没有遇到任何问题。

您是否尝试将所有程序集放在exe所在的子目录下? 我现在不记得细节,但是有一个步骤列表,记录了加载程序查找程序集/类型的确切位置和顺序。