使用Ninject绑定相同类型的多个版本
本文关键字:版本 同类型 Ninject 绑定 使用 | 更新日期: 2023-09-27 18:17:59
我正在使用Ninject创建一组"插件",例如:
Bind<IFoo>().To<Class1>();
Bind<IFoo>().To<Class2>();
Bind<IFoo>().To<Class3>();
…然后我用kernel.GetAll<IFoo>()
,迭代结果。每个Class1/Class2/Class3
实现IFoo
当然,并有构造函数,有一堆参数也由Ninject注入,例如Class1
的构造函数是 public Class1(IBar bar, IBaz baz)
, IBar
和IBaz
都由Ninject注入。到目前为止一切顺利。
然而,现在我想有Class1
的两个不同的"版本",都绑定到IFoo
,不同的只是在构造时传递的值。例如,假设Class1
构造函数现在是 public Class1(IBar bar, IBaz baz, bool myParameter)
,我想做以下操作:
Bind<IFoo>().To<Class1>(); //Somehow pass 'true' to myParameter here
Bind<IFoo>().To<Class1>(); //Somehow pass 'false' to myParameter here
Bind<IFoo>().To<Class2>();
Bind<IFoo>().To<Class3>();
…然后,当我调用kernel.GetAll<IFoo>()
时,我希望返回IFoo
的4个版本(Class1
"真"版本,Class1
假版本,Class2
和Class3
)。
以下是我尝试过的一些想法,但没有一个奏效:
1)我可以分离类(例如Class1True
和Class1False
),其中一个派生于另一个,并绑定到它们。问题是,当我必须为许多类这样做时,这种解决方案并不能真正扩展—我最终用许多无用的类污染了我的类层次结构,并且当我想要传递的构造函数参数比bool更复杂时,问题变得更糟。现实的例子:
Bind<IDrawingTool>().To<Brush>(); //Somehow pass '5' to brushThickness to create a fine brush
Bind<IDrawingTool>().To<Brush>(); //Somehow pass '25' to brushThickness to create a medium brush
Bind<IDrawingTool>().To<Brush>(); //Somehow pass '50' to brushThickness to create a coarse brush
Bind<IDrawingTool>().To<Pencil>();
Bind<IDrawingTool>().To<SprayCan>();
当然,这只是无限个可能构型中的一个。为每个笔刷厚度创建一个新类似乎是错误的。
2)我研究了使用. method绑定的可能性,就像这样:Bind<IDrawingTool>().ToMethod(c => new Brush(5));
Bind<IDrawingTool>().ToMethod(c => new Brush(25));
Bind<IDrawingTool>().ToMethod(c => new Pencil());
但是在这种情况下,我对以下内容感到困惑:
a)如果Brush()构造函数实际上也需要其他参数,必须通过Ninject注入呢?
b)实际上允许多个tommethod绑定吗?
c)这将与InSingletonScope()工作吗?
所以总结一下:绑定到同一类型的多个"版本"的好方法是什么?
为同一类型创建两个绑定是完全可以的,它们只是在参数上有所不同。所以你要做的是:
Bind<IFoo>().To<Class1>().WithConstructorArgument("boolParameterName", true);
Bind<IFoo>().To<Class1>().WithConstructorArgument("boolParameterName", false);
使用WithConstructorArgument
传递参数。您可以让Ninject通过名称匹配参数—在上面的示例中,Class1
函数需要一个名称恰好为boolParameterName
的bool参数。或者您可以匹配类型,在这种情况下,在构造函数中只能有一个该类型的参数。例如:WithConstructorArgument(typeof(bool), true)
。所有没有在WithConstructorArgument
中指定的参数都将"照常"获得ctor-inject。
完整的工作示例(使用xunit和FluentAssertions内核包):
public interface IBar { }
public class Bar : IBar { }
public interface IFoo { }
class Foo1 : IFoo
{
public Foo1(IBar bar) { }
}
class Foo2 : IFoo
{
public Foo2(IBar bar, bool theParametersName) { }
}
[Fact]
public void FactMethodName()
{
var kernel = new StandardKernel();
kernel.Bind<IBar>().To<Bar>();
kernel.Bind<IFoo>().To<Foo1>();
kernel.Bind<IFoo>().To<Foo2>().WithConstructorArgument("theParametersName", true);
kernel.Bind<IFoo>().To<Foo2>().WithConstructorArgument("theParametersName", false);
List<IFoo> foos = kernel.GetAll<IFoo>().ToList();
foos.Should().HaveCount(3);
}
如果你不使用任何条件绑定,当容器检测到依赖时,在运行时解析那些"多个版本"将会由于歧义导致异常。它当然可以在基于"服务定位器"的访问中工作,但在真正的DI组合对象图中,使用这种方法会遇到麻烦。
在您所描述的场景中,如果存在以下假设情况,则会产生这种歧义:
public class MyDraw
{
public MyDraw(IDrawingTool drawingTool)
{
// Your code here
}
}
kernel.Bind<IDrawingTool>().ToMethod(c => new Brush(5));
kernel.Bind<IDrawingTool>().ToMethod(c => new Brush(25));
kernel.Bind<IDrawingTool>().ToMethod(c => new Pencil);
// Runtime exception due to ambiguity: How would the container know which drawing tool to use?
var md = container.Get<MyDraw>();
但是,如果要注入这个类:
public class MyDraw
{
public MyDraw(IEnumerable<IDrawingTool> allTools)
{
// Your code here
}
}
由于多注入,这将工作。容器将简单地调用与IDrawingTool
匹配的所有绑定。在这种情况下,允许多个绑定,甚至是ToMethod(...)
绑定。
你需要做的是依赖于机制,如命名绑定或上下文绑定(使用WhenXXX(...)
语法),让目标注入来确定它需要的具体实现。Ninject对此有广泛的支持,并且实际上是其核心DI框架的定义特性之一。