2
votes

At the moment I am refactoring an old project and trying to implement dependency injection with Ninject.

I came across a more or less simple problem. I found a working solution for this, but i am not sure if this is the best way to solve this.

I try to explain the situation as precisely as possible:

I have an interface ITool:

public interface ITool 
{
    string Caption { get; set; }
    string Name { get; }

    IAction[] Actions { get; }
}

There is also an abstract class AbstractTool (the concrete content is not relevant for the question)

Further I have the derived class GenericTool:

public class GenericTool : AbstractTool 
{
    private readonly string furtherInformation;

    public override string FurtherInformation
    { 
       get { return furtherInformation; } 
    }

    public GenericTool (string furtherInformation, string caption, string name, Action[] actions)
        : base(caption, name, actions)
    {
       this.furtherInformation= furtherInformation;
    }        
}

At one point this GenericTool is used and instantiate a couple of times like this:

new OtherObject(
{
   new List<GenericTool>
   {
      new GenericTool("info1","caption1","name1", new IActions[]
         { new Action1(), new Action2()}),
      new GenericTool("info2","caption2","name2", new IActions[]
         { new Action3(), new Action4()}),
        ...
   }
}...

I wanted to resolve this with only one call:

kernel.Get<OtherObject>();

But I wasn't sure how to bind the different actions (Action1, 2, 3, 4) that they will resolved correctly to the belonging GenericTool.

My solution was to create a derived class of GenericTool and used the NamedAttribute of Ninject

public class SpecialTool1 : GenericTool
{
    public SpecialTool1([Named("SpecialTool1Action")] IAction[] actions)
          : base("info1", "caption1","name1", actions)
    {}
}

public class SpecialTool2 : GenericTool
{
    public SpecialTool2([Named("SpecialTool2Action")] IAction[] actions)
        : base("info2", "caption2","name2", actions)
    {}
}

The binding for the actions :

Bind<IAction>.To<Action1>().Named("SpecialTool1Action");
Bind<IAction>.To<Action2>().Named("SpecialTool1Action");
Bind<IAction>.To<Action3>().Named("SpecialTool2Action");
Bind<IAction>.To<Action4>().Named("SpecialTool2Action");

With this I get the required result. But I have to create a lot of small classes like SpecialTool1.

So my second work around this:

Bind<IAction>.To<Action1>().Named("SpecialTool1Action");
Bind<IAction>.To<Action2>().Named("SpecialTool1Action");

Bind<ITool>().To<GenericTool>()                        
   .WithConstructorArgument("furtherInformation","info1")
   .WithConstructorArgument("caption", "caption1")
   .WithConstructorArgument("name", "name1")
   .WithConstructorArgument("actions", GetLateNamedConstructorArgument);

The function GetLateNamedConstructorArgument:

private IAction[] GetLateNamedConstructorArgument(IContext context)
{
    IEnumerable<IAction> actions= context.Kernel.
            GetAll<IAction>("SpecialTool1Action");
    return actions.ToArray();
}

With the last solution I got the same result without creating dozens of classes.

But is there a better way to solve this? Can I declare the name for the required bindings in a other way? Is there maybe a complete other work around?

EDIT

I imagine something like this:

Bind<ITool>().To<GenericTool>()
   ...
.WithConstructorArgument("actions", [Named("SpecialTool1Action")]);

And Ninject injects all actions with the SpecialTool1Action name.

1

1 Answers

0
votes

The problem with going down this route is that, as mentioned in the documentation for named bindings, it's typically the Service Location anti-pattern.

A better approach here is to use the Abstract Factory pattern.

The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes.

Basically instead of letting the DI container spread through your code to meet your complex requirements, you instead request factories from it and use them to create the required instances.

Ninject provides a few ways to implement this, such as implementing your own Providers or Factory Methods.

In Ninject v3 you can use the Factory interface extension. That documentation link has a whole bunch of example code. Here is the main one to illustrate the concept:

public class Foo
{
    readonly IBarFactory barFactory;

    public Foo(IBarFactory barFactory)
    {
        this.barFactory = barFactory;
    }

    public void Do()
    {
        var bar = this.barFactory.CreateBar();
        ...
    }
}

public interface IBarFactory
{
    Bar CreateBar();
}