我正在学习 DI,最近做了我的第一个项目。
在这个项目中,我实现了存储库模式。我有接口和具体的实现。我想知道是否可以将我的接口的实现构建为“插件”,即我的程序将动态加载的 dll。
所以程序可以随着时间的推移而改进,而不必重建它,您只需将 dll 放在“插件”文件夹中,更改设置,瞧!
这可能吗?Ninject 可以帮助解决这个问题吗?
虽然Sean Chambers 的解决方案适用于您控制插件的情况,但它不适用于插件可能由第三方开发并且您不希望它们必须依赖于编写 ninject 模块的情况。
使用Ninject的Conventions Extension很容易做到这一点:
public static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Scan(scanner => {
scanner.FromAssembliesInPath(@"Path\To\Plugins");
scanner.AutoLoadModules();
scanner.WhereTypeInheritsFrom<IPlugin>();
scanner.BindWith<PluginBindingGenerator<IPlugin>>();
});
return kernel;
}
private class PluginBindingGenerator<TPluginInterface> : IBindingGenerator
{
private readonly Type pluginInterfaceType = typeof (TPluginInterface);
public void Process(Type type, Func<IContext, object> scopeCallback, IKernel kernel)
{
if(!pluginInterfaceType.IsAssignableFrom(type))
return;
if (type.IsAbstract || type.IsInterface)
return;
kernel.Bind(pluginInterfaceType).To(type);
}
}
然后,您可以使用kernel.GetAll<IPlugin>()
.
这种方法的优点是:
这个问题适用于我在这里提供的相同答案:NInject 可以按需加载模块/程序集吗?
我很确定这就是你要找的东西:
var kernel = new StandardKernel();
kernel.Load( Assembly.Load("yourpath_to_assembly.dll");
如果您在 Ninject.dll 中查看带有反射器的 KernelBase,您将看到此调用将递归加载已加载程序集中的所有模块(Load 方法采用 IEnumerable)
public void Load(IEnumerable<Assembly> assemblies)
{
foreach (Assembly assembly in assemblies)
{
this.Load(assembly.GetNinjectModules());
}
}
我将它用于我不希望直接程序集引用会非常频繁地更改的东西的场景,并且我可以换出程序集以为应用程序提供不同的模型(假设我有适当的测试)
扩展基于 v.2 的 @ungood 好答案,使用 Ninject 的 v.3(目前在 RC3 上),它可以变得更加容易。您不再需要任何 IPluginGenerator,只需编写:
var kernel = new StandardKernel();
kernel.Bind(scanner => scanner.FromAssembliesInPath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location))
.SelectAllClasses()
.InheritedFrom<IPlugin>()
.BindToAllInterfaces());
请注意,我正在寻找在应用程序的同一路径中实现 IPlugin(将您的界面放在此处)的插件。
您可以使用普通的 C# 反射轻松地做到这一点,您不需要任何额外的技术。
网上有很多例子,例如 http://www.codeproject.com/KB/cs/c__plugin_architecture.aspx
通常,在您的主应用程序中,您需要加载实现插件的程序集,例如:
ass = Assembly.Load(name);
然后您需要创建插件的一个实例。如果您知道类的名称,它将如下所示:
ObjType = ass.GetType(typename);
IPlugin plugin = (IPlugin)Activator.CreateInstance(ObjType);
然后你就用它。
看看托管可扩展性框架。http://www.codeplex.com/MEF
我认为不需要框架。本教程解决了您的问题http://www.codeproject.com/KB/cs/c__plugin_architecture.aspx
我得到了这个作为 Activator.CreateInstance + Ninject 的成功,只是想指出这方面的一些事情 - 希望它会激发某人对这个问题提出一个真正的杀手级答案。
如果您还没有遇到自动扫描模块和类并在 Ninject 中正确注册它们的麻烦,并且仍在通过 Activator.CreateInstance 创建您的插件,那么您可以 post-CreateInstance 将依赖项注入 via
IKernel k = ...
var o = Activator.CreateInstance(...);
k.Inject( o );
当然,这只是通往http://groups.google.com/group/ninject/browse_thread/thread/880ae2d14660b33c之类的临时解决方案
有多种方法可以解决这个问题,您已经完成了通过预定义接口实现具体实现的主要目标。实际上,如果您的界面保持稳定,您应该能够构建您的核心应用程序。
但是,我不确定实施将如何与 Ninject 一起使用。您可以使用提供者模型或反射来执行此操作 - 尽管我认为反射是多余的,如果您不是绝对需要这样做的话。
使用提供者模型方法,您将文件放在 /bin 文件夹或您正在探测的任何其他文件夹中,并调整 .config 文件以反映提供者的存在。如果您有一个特定的“插件”文件夹,您可以创建一个在应用程序启动时调用的方法,并定期调用,否则,扫描新的或删除的实例并重新加载提供程序。
这将在 C# 或 VB 下的 ASP.NET 中工作。但是,如果您正在执行某种其他应用程序,则需要考虑另一种方法。提供者实际上只是 Microsoft 对Strategy Pattern的转折。
问题是,如果您在模块加载中设置的对象在程序内部使用,您可能需要重新编译。原因是你的程序可能没有你的类的最新版本的程序集。例如,如果您为其中一个接口创建一个新的具体类,假设您更改了插件 dll。现在,Injector 将加载它,很好,但是当它在您的程序 (kernel.get(...)) 中返回时,您的程序可能没有程序集并且会抛出错误。
我正在谈论的示例:
BaseAuto auto = kernel.Get<BaseAuto>();//Get from the NInjector kernel your object. You get your concrete objet and the object "auto" will be filled up (interface inside him) with the kernel.
//Somewhere else:
public class BaseModule : StandardModule
{
public override void Load(){
Bind<BaseAuto>().ToSelf();
Bind<IEngine>().To<FourCylinder>();//Bind the interface
}
}
如果您创建了一个名为 SixCylinder 的新 FourCylinder,那么您的实际程序将不会引用您的新对象。因此,一旦您从插件加载 BaseModule.cs,您可能会遇到一些参考问题。为了能够做到这一点,您需要使用您的插件分发这个具体实现的新 dll,该插件将具有注入器将接口加载到具体类所需的模块。这可以毫无问题地完成,但是您开始拥有一个驻留在从插件加载的整个应用程序,并且在某些方面可能会出现问题。意识到。
但是,如果您确实需要一些插件信息,您可以从 CodeProject 获得一些教程。