11

Introduction:

I'm using the Ninject Factory Extension to inject simple object factories into my service classes.

Here is my interface and two classes that implement it:

public interface ICar
{
    void Drive();
    void Stop();
}

public class Mercedes : ICar
{
    public void Drive()
    {
      Do mercedes drive stuff...
    }

    public void Stop()
    {
      Do mercedes stop stuff...  
    }

}

public class Ferrari : ICar
{
    public void Drive()
    {
      Do ferrari drive stuff...
    }

    public void Stop()
    {
      Do ferrari stop stuff...  
    }
}

Here is my object factory to dynamically create a car at runtime:

public interface ICarFactory
{
   ICar CreateCar(string carType);
}

public class CarFactory : ICarFactory 
{
   public ICar CreateCar(string carType)
   {
       ICar car;

       switch (type)
       {
           case "mercedes":
                car = new Mercedes();
           break;

           case "ferrari":
               car = new Ferrari();
           break;
       }

       return car;
    }
 }

Then use ninject factory extension "ToFactory" method to bind my car factory interface:

public class CarModule : Ninject.Modules.NinjectModule
{
      public override void Load()
      {
           Bind<ICarFactory>().ToFactory();
      }
}

Problem:

My factory gets injected into my service class as expected and can be used to create car objects, however ninject blows up here because it cannot properly resolve ICar to the concrete type ie. Mercedes or Ferrari returned by the factory CreateCar() method.

public CarService(string carType, ICarFactory carFactory)
{
   var car = carFactory.CreateCar(carType);
}

Question:

Assuming the factory pattern I'm using here is compatible with how ninject factory extension is intended to work, how do I setup the bindings for ICar -> Ferrari, ICar -> Mercedes etc. so that they may be dynamically resolved at run time with this approach?

Thanks!

mmacneil007
  • 578
  • 1
  • 8
  • 20
  • I'm not really sure what you expect to happen. Are you expecting that, if the value of `carType` is `"mercedes"`, then the type of `var car` should be `Mercedes` instead of `ICar`? C# doesn't work like this. – Paul Phillips Jan 22 '13 at 03:37
  • Where are your bindings for `ICar`? The Ninject factories extension is probably taking control here and is looking for your `ICar` bindings. When it doesn't find them it will blow up.. – Simon Whitehead Jan 22 '13 at 03:44

1 Answers1

24

There is an example of custom factory at ninject.extension.factory wiki

First, create custom implementation of StandardInstanceProvider to override default factory behaviour

public class UseFirstArgumentAsNameInstanceProvider : StandardInstanceProvider
{
    protected override string GetName(System.Reflection.MethodInfo methodInfo, object[] arguments)
    {
        return (string)arguments[0];
    }

    protected override ConstructorArgument[] GetConstructorArguments(MethodInfo methodInfo, object[] arguments)
    {
        return base.GetConstructorArguments(methodInfo, arguments).Skip(1).ToArray();
    }
}

At CarModule specify UseFirstArgumentAsNameInstanceProvider (custom instance provider) for ICarFactory factory and names for dependencies

public class CarModule : NinjectModule
{
    public override void Load()
    {
        Bind<ICarFactory>()
            .ToFactory(() => new UseFirstArgumentAsNameInstanceProvider());

        Bind<ICar>()
            .To<Mercedes>()
            .Named("Mercedes");

        Bind<ICar>()
            .To<Ferrari>()
            .Named("Ferrari");
    }
}

Resolve factory and dependencies

var factory = kernel.Get<ICarFactory>();

var mercedes = factory.CreateCar("Mercedes");
var ferrari = factory.CreateCar("Ferrari");

ps: here is full example

Community
  • 1
  • 1
Akim
  • 8,469
  • 2
  • 31
  • 49
  • Great answer Akim - I implemented this and it works perfectly. Thanks!! – mmacneil007 Jan 22 '13 at 12:58
  • @Akim Thanks for the feedback, I deleted my answer as this is obviously the right one. Although could have done without the -1 as my answer would have also worked given the code impl he was using. – Gent Jan 22 '13 at 23:46
  • @Gent, trick is to avoid to write implementation of `ICarFactory`, and make `ninject` to implements it according to mapping – Akim Jan 23 '13 at 08:58
  • @Akim thanks for this answer - do you have any experience with [Ninject Conventions](https://github.com/ninject/ninject.extensions.conventions/wiki) extension, and how you would write the `ICar` bindings using that way instead? I posted a [question](http://stackoverflow.com/q/15868820/533958) building off this answer - would appreciate any guidance. – Jeff Apr 08 '13 at 03:17
  • 1
    Hey @Akim, How would you bind to a default car if no name matches. Like -- Bind().To(); – labroo Feb 17 '14 at 23:04