我正在学习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>()
来获取所有已加载的插件。
这种方法的优点是:
这个问题与我在这里提供的答案有关:Can NInject load modules/assemblies on demand?
我非常确定这就是你要找的答案:
var kernel = new StandardKernel();
kernel.Load( Assembly.Load("yourpath_to_assembly.dll");
如果你使用反射器(reflector)在Ninject.dll中查看KernelBase,你会发现这个调用会递归加载所有已加载程序集中的所有模块(Load方法接受IEnumerable参数)。
public void Load(IEnumerable<Assembly> assemblies)
{
foreach (Assembly assembly in assemblies)
{
this.Load(assembly.GetNinjectModules());
}
}
我在某些场景下使用它,这些场景中我不希望直接引用会经常变化的东西,而我可以更换程序集来向应用程序提供不同的模型(当然,前提是我已经进行了适当的测试)。
在 @ungood 的很好的答案基础上进行扩展,该答案基于 v.2,在使用 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);
然后你只需要使用它。
我在搜索Activator.CreateInstance + Ninject时发现了这个,想指出一些相关的事情 - 希望能激发某人在SO上提供一个真正杀手级别的答案。
如果你还没有费心自动扫描模块和类并正确地将它们注册到Ninject中,并且仍然通过Activator.CreateInstance创建你的插件,那么你可以通过post-CreateInstance注入依赖项。
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 对策略模式的解释。
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,您可能会遇到一些引用问题。要能够这样做,您需要使用包含Injector所需的模块的插件分发此具体实现的新dll,以便加载接口到具体类。这可以无问题完成,但您开始拥有一个完全驻留在插件加载上的整个应用程序,这可能在某些点上有问题。要注意。
但是,如果您确实想要一些插件信息,可以查看 CodeProject教程。