我将我的问题分为短版和长版,供手头时间不多的人使用。
精简版:
我需要一些具有提供者和消费者插件的系统的架构。
提供者应该实现接口(interface) IProvider,消费者应该实现 IConsumer。
正在执行的应用程序应该只知道 IProvider 和 IConsumer。
消费者实现可以询问正在执行的程序集(通过 ServiceProcessor)哪些提供者实现了 InterfaceX 并返回一个 List。
这些 IProvider 对象应该被转换为 InterfaceX(在消费者中),以便能够将消费者 Hook 到 InterfaceX 定义的某些事件上。这将失败,因为执行程序集不知何故不知道此 InterfaceX 类型(转换失败)。解决方案是将 InterfaceX 包含到插件和执行程序集都引用的某个程序集中,但这应该意味着为每个新的提供者/消费者对重新编译,这是非常不可取的。
有什么建议?
长版:
我正在开发某种通用服务,它将使用插件来实现更高级别的可重用性。该服务由使用提供者和消费者的某种观察者模式实现组成。提供者和消费者都应该是主应用程序的插件。让我首先通过列出我的解决方案中的项目来解释该服务的工作原理。
项目 A:用于托管所有插件和基本功能的 Windows 服务项目。 TestGUI Windows 窗体项目用于更轻松的调试。来自项目 B 的 ServiceProcessor 类的一个实例正在做插件相关的事情。该项目的子文件夹“Consumers”和“Providers”包含子文件夹,其中每个子文件夹分别包含一个使用者或提供者插件。
项目 B:一个包含 ServiceProcessor 类的类库(它执行所有插件加载和插件之间的调度等)、IConsumer 和 IProvider。
项目C:一个类库,链接到项目B,由TestConsumer(实现IConsumer)和TestProvider(实现IProvider)组成。 TestProvider 实现了一个额外的接口(interface)(ITest,它本身是从 IProvider 派生的)。
这里的目标是消费者插件可以询问 ServiceProcessor 它有哪些提供者(至少实现 IProvider)。返回的 IProvider 对象应该被转换到它在 IConsumer 实现中实现的另一个接口(interface) (ITest),以便消费者可以将事件处理程序挂接到 ITest 事件。
项目 A 启动时,将加载包含使用者和提供者插件的子文件夹。以下是我迄今为止遇到并试图解决的一些问题。
接口(interface) ITest 曾经驻留在项目 C 中,因为这仅适用于 TestProvider 和 TestConsumer 知道的方法和事件。总体思路是保持项目 A 简单,不知道插件之间的作用。
使用项目 C 中的 ITest 和 TestConsumer 的 Initialize 方法中的代码将 IProvider 转换为 ITest(当实现 ITest 的对象被称为 IConsumer 对象时,这不会在单个类库本身中失败)会发生无效的转换错误.可以通过将 ITest 接口(interface)放入项目 A 引用的项目 B 来解决此错误。这是非常不需要的,因为我们需要在构建新接口(interface)时重新编译项目 A。
我试图将 ITest 放在仅由项目 C 引用的单个类库中,因为只有提供者和使用者需要知道这个接口(interface),但没有成功:加载插件时,CLR 声明找不到引用的项目。这可以通过 Hook 当前 AppDomain 的 AssemblyResolve 事件来解决,但不知何故,这似乎也是不需要的。 ITest又回到了Project B。
我尝试将项目 C 拆分为消费者和提供者的单独项目,并且都加载本身运行良好的程序集:这两个程序集都驻留在 Assemblies 集合或当前的 AppDomain 中:
找到的程序集:Datamex.Projects.Polaris.Testing.Providers,Version=1.0.0.0,Culture=neutral,PublicKeyToken=2813de212e2efcd3
找到的程序集:Datamex.Projects.Polaris.Testing.Consumers,版本=1.0.0.0,Culture=neutral,PublicKeyToken=ea5901de8cdcb258
由于消费者使用提供者,因此从消费者到提供者进行了引用。现在再次触发 AssemblyResolve 事件,说明它需要以下文件:
AssemblyName=Datamex.Projects.Polaris.Testing.Providers,版本=1.0.0.0,文化=中性,PublicKeyToken=2813de212e2efcd3
我的问题:
为什么是这样?这个文件已经加载了吧?
为什么从 IProvider 转换到我知道它实现的某个接口(interface)是不可能的?这可能是因为执行程序本身不知道这个接口(interface),但是这个不能动态加载吗?
我的最终目标:
消费者插件询问 ServiceProcessor 它有哪些提供者实现了接口(interface) x。提供者可以被强制转换到这个接口(interface) x,而无需执行程序集知道接口(interface) x。
有人可以帮忙吗?
提前致谢,
埃里克
最佳答案
我只是尽可能地重新创建您的解决方案,我没有这样的问题。 (警告,后面有很多代码示例......)
第一个项目是应用程序,它包含一个类:
public class PluginLoader : ILoader
{
private List<Type> _providers = new List<Type>();
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
{
}
关于具有插件之间共享接口(interface)的 C# 插件架构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/829597/
我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/
我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在
我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此
我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是
在Cooper的书BeginningRuby中,第166页有一个我无法重现的示例。classSongincludeComparableattr_accessor:lengthdef(other)@lengthother.lengthenddefinitialize(song_name,length)@song_name=song_name@length=lengthendenda=Song.new('Rockaroundtheclock',143)b=Song.new('BohemianRhapsody',544)c=Song.new('MinuteWaltz',60)a.betwee
我正在检查一个Rails项目。在ERubyHTML模板页面上,我看到了这样几行:我不明白为什么不这样写:在这种情况下,||=和ifnil?有什么区别? 最佳答案 在这种特殊情况下没有区别,但可能是出于习惯。每当我看到nil?被使用时,它几乎总是使用不当。在Ruby中,很少有东西在逻辑上是假的,只有文字false和nil是。这意味着像if(!x.nil?)这样的代码几乎总是更好地表示为if(x)除非期望x可能是文字false。我会将其切换为||=false,因为它具有相同的结果,但这在很大程度上取决于偏好。唯一的缺点是赋值会在每次运行
如何在ruby中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL
我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha
我正在使用Rails3.1并在一个论坛上工作。我有一个名为Topic的模型,每个模型都有许多Post。当用户创建新主题时,他们也应该创建第一个Post。但是,我不确定如何以相同的形式执行此操作。这是我的代码:classTopic:destroyaccepts_nested_attributes_for:postsvalidates_presence_of:titleendclassPost...但这似乎不起作用。有什么想法吗?谢谢! 最佳答案 @Pablo的回答似乎有你需要的一切。但更具体地说...首先改变你View中的这一行对此#