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

时间:2014-08-14 19:52:02

标签: c# .net dependency-injection ninject

我正在使用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) < / strong>,Ninject注入了IBarIBaz。到现在为止还挺好。

然而,现在我想要两个不同的版本&#34; Class1的{​​{1}},都绑定到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&#34; true&#34;版本,Class1 false版本,Class2Class3)。 我已阅读Ninject文档但无法找到实现此目的的方法。

以下是我尝试过的一些想法,但没有一个能够很好地运作:

1)我可以将类(例如Class1TrueClass1False)分开,一个派生自另一个,并绑定到它们。问题是,当我必须为许多类做这个时,这个解决方案并没有真正扩展 - 我最终用很多无用的类来污染我的类层次结构,当我想通过构造函数参数时问题变得更糟比布尔更复杂。现实的例子:

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)我研究了使用.ToMethod绑定的可能性,如下所示:

Bind<IDrawingTool>().ToMethod(c => new Brush(5));
Bind<IDrawingTool>().ToMethod(c => new Brush(25));
Bind<IDrawingTool>().ToMethod(c => new Pencil());

但在这种情况下,我对以下内容感到困惑:

a)如果Brush()构造函数实际上还需要其他参数,必须通过Ninject注入,该怎么办?

b)实际上是否允许多个ToMethod绑定?

c)这适用于InSingletonScope()吗?

总结一下:什么是绑定多个&#34;版本的好方法&#34;相同类型?

2 个答案:

答案 0 :(得分:2)

如果您不使用任何条件绑定,请解决这些&#34;多个版本&#34;在运行时,容器检测到依赖关系将导致异常,由于歧义。它肯定可以在基于服务定位器的访问中工作,但是在组成对象图的真正的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
    }
}

由于多次注射,这将起作用。 caontainer只会调用匹配IDrawingTool的所有绑定。在这种情况下,允许多个绑定,甚至ToMethod(...)个。

您需要做的是依靠命名绑定上下文绑定(使用WhenXXX(...)语法等机制来让目标< / strong>注入以确定它需要哪种具体实现.Ninject对此有广泛的支持,实际上是它的核心DI框架的定义特征之一。你可以阅读它here

答案 1 :(得分:2)

为同一类型创建两个绑定完全没问题,这两个绑定仅在参数方面有所不同。 所以你要做的是:

Bind<IFoo>().To<Class1>().WithConstructorArgument("boolParameterName", true);
Bind<IFoo>().To<Class1>().WithConstructorArgument("boolParameterName", false);

使用WithConstructorArgument传递参数。您可以通过名称使Ninject与参数匹配 - 在上面的示例中,Class1 ctor需要具有名称正好为boolParameterName的bool参数。或者您可以匹配类型,在这种情况下,您只能在构造函数中具有该类型的一个参数。示例:WithConstructorArgument(typeof(bool), true)。 您不需要WithConstructorArgument指定的所有参数都可以像往常一样使用ctor-inject&#34;


完整的工作示例(使用xunit和FluentAssertions nuget包):

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);
    }