0

I have an interface, let's say it's IDrawingTool. I have multiple classes implementing this interface, let's say PencilTool, PenTool, YellowMarkerTool, etc. I am usually binding more than one of these classes in Ninject, and I always access IDrawingTool instances by calling kernel.GetAll<IDrawingTool>. So far so good.

Now, I want to create a new IDrawingTool implementation, ConfigurableBrushTool, that can be configured in many different ways (let's say brush color, brush thickness, etc).

I'd like to be able to have some sort of a "factory"/"provider" that will let me inject multiple IDrawingTools (i.e. multiple ConfigurableBrushTools with different configurations). That is, I want to be able to do something like the following:

kernel.Bind<IDrawingTool>.To<PencilTool>();
kernel.Bind<IDrawingTool>.To<PenTool>();
kernel.Bind<IDrawingTool>.ToTypeProvider<ConfigurableBrushToolProvider>();
//...where ConfigurableBrushToolProvider reads e.g. 50 different
//brush configurations (color, thickness, etc) from the database/file/network/whatever
//and binds 50 different ConfigurableBrushTool instances.
//Of course, .ToTypeProvider() doesn't really exist :)

//Later on:
var tools = kernel.GetAll<IDrawingTool>(); //Should return pencil, pen and all 50 brushes

I haven't been able to find a way to make this happen with Ninject.

I've looked at implementing IProvider / Provider<T>, but it only lets me return one instance of the type I'm providing, there's no way to return multiple instances in bulk.

I've also looked at IMissingBindingResolver, and it's very close to what I need: If I create such a resolver for IDrawingTool, I am able to return multiple bindings for multiple ConfigurableBrushTools. However, it only works if the binding is missing (i.e. if there are no other IDrawingTool bindings). As soon as I add my PenTool and PencilTool bindings, the binding for IDrawingTool is no longer "missing" and so my custom resolver is no longer invoked.

Is there a way to make my scenario happen? I.e. how can I bind an interface to both (1) specific types implementing the interface, and (2) a "factory"/"provider" of many instances that implement the interface, such that GetAll will return all of the bindings, both (1) and (2)?

Eugene Osovetsky
  • 6,443
  • 2
  • 38
  • 59

2 Answers2

1

First of all, looks like there is no OOTB feature to make multiple bindings from one binding set in module (at least I didn't find any obvious one).
Secondly, I managed to create some PoC with custom Kernel that allows you to create one-to-many binding with one .Bind.To call. Is is based on overriding IKernel.GetBindings(..) method and finding all bindings with special parameter. Then we can remove them, replacing by amount of custom generated.
However, I think such logic will have huge impact on performance if not carefully optimized (which is not done for PoC anyway) and should make it to production only if it is really needed. This PoC you can find here.
As for me, it is much better approach to refactor existing code to inject a factory returning array created on-the-fly.

  • Thanks! Too bad there's no out-of-the-box way to do it... I appreciate the work you've put into this, despite the perf concerns your code achieves exactly what I need (though realistically I'll probably choose the refactoring approach), but in any case, bounty's yours. – Eugene Osovetsky Apr 24 '16 at 20:10
  • Thanks, Eugene, hope this example would anyway help you to understand possibilities of Ninject. – Konstantin Peshekhonov Apr 25 '16 at 07:35
0

How about binding a custom collection (circumventing multi-injection) like:

public interface IToolList : IReadOnlyList<IDrawingTool> {}

and have it's implementation return both, bound tools and the ones "created" from database, something like:

public class ToolList : List<IDrawingTool>, IToolList
{
    public ToolList(IDrawingTool[] boundTools,... otherDependencies)
    {
       ... create all tools here
    }
}
BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
  • There's a large, existing codebase which already does GetAll in a bunch of places - I'd rather not change all these places to use IToolList (also, another developer down the line won't know that in the special case of IDrawingTool you need to access it via IToolList rather than just the standard GetAll). To be clear, my real use case is not about drawing tools but complex business objects : ), an there are many more things Ninject-ed. Your answer works as a workaround, but I'd like to know if it's possible to make Ninject do precisely what I need. – Eugene Osovetsky Apr 17 '16 at 23:01