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

时间:2021-04-12 17:03:31

I am using Ninject to create a set of "plugins", e.g. I have:

我正在使用Ninject创建一组“插件”,例如我有:

Bind<IFoo>().To<Class1>(); 
Bind<IFoo>().To<Class2>();
Bind<IFoo>().To<Class3>();

... and later on I use kernel.GetAll<IFoo>() and iterate over the results. Each of Class1/Class2/Class3 implement IFoo of course, and have constructors that have a bunch of parameters also injected by Ninject, for example the constructor for Class1 is public Class1(IBar bar, IBaz baz), with both IBar and IBaz injected by Ninject. So far so good.

...稍后我使用kernel.GetAll ()并迭代结果。 Class1 / Class2 / Class3中的每一个当然都实现了IFoo,并且具有也由Ninject注入的一堆参数的构造函数,例如Class1的构造函数是public Class1(IBar bar,IBaz baz),IBar和IBaz都注入了Ninject。到现在为止还挺好。

However, now I want to have two different "versions" of Class1, both bound to IFoo, differing only in a value passed at construction time. That is, for example, suppose the Class1 constructor was now public Class1(IBar bar, IBaz baz, bool myParameter), and I want to do the following:

但是,现在我想要有两个不同的Class1版本,它们都绑定到IFoo,只是在构造时传递的值不同。也就是说,例如,假设Class1构造函数现在是公共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>();

... Then, when I call kernel.GetAll<IFoo>(), I want 4 versions of IFoo returned (Class1 "true" version, Class1 false version, Class2 and Class3). I've read through the Ninject documentation and can't find a way to do this.

...然后,当我调用kernel.GetAll ()时,我想要返回4个版本的IFoo(Class1“true”版本,Class1 false版本,Class2和Class3)。我已经阅读了Ninject文档,但无法找到实现此目的的方法。

Here are some ideas I tried, but none of them work well:

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

1) I could just separate classes (e.g. Class1True and Class1False), with one deriving from another, and bind to them. The problem is that this solution doesn't really scale when I have to do this for many classes - I end up polluting my class hierarchy with a lot of useless classes, and the problem becomes worse when the constructor parameter I want to pass is anything more complex than a bool. Realistic example:

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

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

Of course, this is just one possible configuration of infinitely many possible ones. Creating a new class for each brush thickness seems wrong.

当然,这只是无限多种可能配置的一种可能配置。为每个画笔厚度创建一个新类似乎是错误的。

2) I looked into the possibility of using a .ToMethod binding, something like this:

2)我研究了使用.ToMethod绑定的可能性,如下所示:

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

But in this case I'm confused about the following:

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

a) What if the Brush() constructor actually requires other parameters as well, that must be injected via Ninject?

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

b) Are multiple ToMethod bindings actually allowed?

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

c) Would this work with InSingletonScope()?

c)这适用于InSingletonScope()吗?

So to summarize: What is a good way to bind to multiple "versions" of the same type?

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

2 个解决方案

#1


2  

It's perfectly fine to create two bindings for the same type, which differ only in parameters. So what you've got to do is:

为同一类型创建两个绑定非常好,这些绑定仅在参数方面有所不同。所以你要做的是:

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

Use the WithConstructorArgument to pass the parameter. You can either have Ninject match the parameter by the name - in the above example the Class1 ctor would need to feature a bool parameter whose name is exactly boolParameterName. Or you can match the type, in which case you could only have one parameter of that type in the constructor. Example: WithConstructorArgument(typeof(bool), true). All the parameters which you don't specify by WithConstructorArgument get ctor-inject "as usual".

使用WithConstructorArgument传递参数。您可以通过名称使Ninject与参数匹配 - 在上面的示例中,Class1 ctor需要具有bool参数,其名称恰好是boolParameterName。或者您可以匹配类型,在这种情况下,您只能在构造函数中具有该类型的一个参数。示例:WithConstructorArgument(typeof(bool),true)。您没有通过WithConstructorArgument指定的所有参数都“照常”进行ctor-inject。


Complete working example (using xunit and FluentAssertions nuget packages):

完整的工作示例(使用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);
    } 

#2


2  

If you don't use any conditional bindings, resolving those "multiple versions" at runtime when the container detects a dependency will result in an exception, due to ambiguity. It surely could work in a "service-locator"-based access, but in true DI composing the object graph, you'll run into trouble using this approach.

如果您不使用任何条件绑定,则在容器检测到依赖项时在运行时解析这些“多个版本”将导致异常,这是由于模糊。它肯定可以在基于“服务定位器”的访问中工作,但是在组成对象图的真正的DI中,使用这种方法会遇到麻烦。

In your depicted scenario, this ambiguity would arise should the following hypothetical situation existed:

在您描绘的场景中,如果存在以下假设情况,则会出现这种模糊性:

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

However, if you have this class to be injected:

但是,如果您要注入此类:

public class MyDraw
{
    public MyDraw(IEnumerable<IDrawingTool> allTools)
    {
        // Your code here
    }
}

This would work due to multi-injection. The caontainer would simply invoke all bindings that match IDrawingTool. In this case, multiple bindings are allowed, even ToMethod(...) ones.

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

What you need to do is rely upon mechanisms such as Named Bindings or Contextual Bindings (using WhenXXX(...) syntax, to let the target of injection to determine which concrete implementation it requires. Ninject has extensive support for this and actually is one of the defining features for it's core DI Framework. You can read about it here.

你需要做的是依靠命名绑定或上下文绑定等机制(使用WhenXXX(...)语法)让注入目标确定它需要哪个具体实现.Ninject对此有广泛的支持,实际上是一个它的核心DI框架的定义功能。你可以在这里阅读它。

#1


2  

It's perfectly fine to create two bindings for the same type, which differ only in parameters. So what you've got to do is:

为同一类型创建两个绑定非常好,这些绑定仅在参数方面有所不同。所以你要做的是:

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

Use the WithConstructorArgument to pass the parameter. You can either have Ninject match the parameter by the name - in the above example the Class1 ctor would need to feature a bool parameter whose name is exactly boolParameterName. Or you can match the type, in which case you could only have one parameter of that type in the constructor. Example: WithConstructorArgument(typeof(bool), true). All the parameters which you don't specify by WithConstructorArgument get ctor-inject "as usual".

使用WithConstructorArgument传递参数。您可以通过名称使Ninject与参数匹配 - 在上面的示例中,Class1 ctor需要具有bool参数,其名称恰好是boolParameterName。或者您可以匹配类型,在这种情况下,您只能在构造函数中具有该类型的一个参数。示例:WithConstructorArgument(typeof(bool),true)。您没有通过WithConstructorArgument指定的所有参数都“照常”进行ctor-inject。


Complete working example (using xunit and FluentAssertions nuget packages):

完整的工作示例(使用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);
    } 

#2


2  

If you don't use any conditional bindings, resolving those "multiple versions" at runtime when the container detects a dependency will result in an exception, due to ambiguity. It surely could work in a "service-locator"-based access, but in true DI composing the object graph, you'll run into trouble using this approach.

如果您不使用任何条件绑定,则在容器检测到依赖项时在运行时解析这些“多个版本”将导致异常,这是由于模糊。它肯定可以在基于“服务定位器”的访问中工作,但是在组成对象图的真正的DI中,使用这种方法会遇到麻烦。

In your depicted scenario, this ambiguity would arise should the following hypothetical situation existed:

在您描绘的场景中,如果存在以下假设情况,则会出现这种模糊性:

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

However, if you have this class to be injected:

但是,如果您要注入此类:

public class MyDraw
{
    public MyDraw(IEnumerable<IDrawingTool> allTools)
    {
        // Your code here
    }
}

This would work due to multi-injection. The caontainer would simply invoke all bindings that match IDrawingTool. In this case, multiple bindings are allowed, even ToMethod(...) ones.

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

What you need to do is rely upon mechanisms such as Named Bindings or Contextual Bindings (using WhenXXX(...) syntax, to let the target of injection to determine which concrete implementation it requires. Ninject has extensive support for this and actually is one of the defining features for it's core DI Framework. You can read about it here.

你需要做的是依靠命名绑定或上下文绑定等机制(使用WhenXXX(...)语法)让注入目标确定它需要哪个具体实现.Ninject对此有广泛的支持,实际上是一个它的核心DI框架的定义功能。你可以在这里阅读它。