使用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) IBarIBaz都由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假版本,Class2Class3)。

我已经通读了Ninject文档,但找不到这样做的方法。

以下是我尝试过的一些想法,但没有一个奏效:

1)我可以分离类(例如Class1TrueClass1False),其中一个派生于另一个,并绑定到它们。问题是,当我必须为许多类这样做时,这种解决方案并不能真正扩展—我最终用许多无用的类污染了我的类层次结构,并且当我想要传递的构造函数参数比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()工作吗?

所以总结一下:绑定到同一类型的多个"版本"的好方法是什么?

使用Ninject绑定相同类型的多个版本

为同一类型创建两个绑定是完全可以的,它们只是在参数上有所不同。所以你要做的是:

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框架的定义特性之一。