1
votes

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

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.

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:

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.

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:

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:

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?

b) Are multiple ToMethod bindings actually allowed?

c) Would this work with InSingletonScope()?

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

2

2 Answers

2
votes

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".


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

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
votes

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.

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.

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.