Using Ninject custom instance providers to bind successfully using factory method argument to resolve

1.2k Views Asked by At

I've been studying this accepted answer to a similar question in which what I believe is a concrete factory returns an implementation based on a string argument on the factory method matching a named binding on the concrete implementation.

I'm struggling to get a slightly more complex example to work properly when the factory is an abstract factory, and I wish to use Ninject convention-based binding. Consider the following test:

[Fact]
public void VehicleBuilderFactory_Creates_Correct_Builder_For_Specified_Client()
{
    // arrange
    StandardKernel kernel = new StandardKernel();
    kernel.Bind(typeof (IVehicleBuilderFactory<,>))
        .ToFactory(() => new UseFirstArgumentAsNameInstanceProvider())
        .InSingletonScope();

    kernel.Bind(scanner => scanner
                    .FromThisAssembly()
                    .SelectAllClasses()
                    .WhichAreNotGeneric()
                    .InheritedFrom(typeof(IVehicleBuilder<>))
                    .BindAllInterfaces());

    var bicycleBuilderFactory = 
        kernel.Get<IVehicleBuilderFactory<IVehicleBuilder<BlueBicycle>, BlueBicycle>>();
    string country = "Germany";
    string localizedColor = "blau";

    // act
    var builder = bicycleBuilderFactory.Create<IVehicleBuilder<BlueBicycle>>(country);
    Bicycle Bicycle = builder.Build(localizedColor);

    // assert
    Assert.IsType<BlueBicycleBuilder_Germany>(builder);
    Assert.IsType<BlueBicycle>(Bicycle);
    Assert.Equal(localizedColor, Bicycle.Color);
}

Here's where I try juggling with torches & knives 'cause I saw it on the internet once:

public class UseFirstArgumentAsNameInstanceProvider : StandardInstanceProvider
{
    protected override string GetName(MethodInfo methodInfo, object[] arguments) {
        return methodInfo.GetGenericArguments()[0].Name + "Builder_" + (string)arguments[0];
        // ex: Germany -> 'BlueBicycle' + 'Builder_' + 'Germany' = 'BlueBicyleBuilder_Germany'
    }

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

I get stabbed and set ablaze when I try to assign bicycleBuilderFactory with this error:

System.InvalidCastException was unhandled by user code
  Message=Unable to cast object of type 'Castle.Proxies.ObjectProxy' to type 'Ninject.Extensions.Conventions.Tests.IVehicleBuilderFactory`2[Ninject.Extensions.Conventions.Tests.IVehicleBuilder`1[Ninject.Extensions.Conventions.Tests.BlueBicycle],Ninject.Extensions.Conventions.Tests.BlueBicycle]'.
  Source=System.Core
  StackTrace:
       at System.Linq.Enumerable.<CastIterator>d__b1`1.MoveNext()
       at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
       at Ninject.ResolutionExtensions.Get[T](IResolutionRoot root, IParameter[] parameters) in c:\Projects\Ninject\ninject\src\Ninject\Syntax\ResolutionExtensions.cs:line 37
       at Ninject.Extensions.Conventions.Tests.NinjectFactoryConventionsTests.VehicleBuilderFactory_Creates_Correct_Builder_For_Specified_Client() in C:\Programming\Ninject.Extensions.Conventions.Tests\NinjectFactoryConventionsTests.cs:line 40
  InnerException: 

Is it possible to bind using the ToFactory() method and custom provider, using the factory method argument ("Germany") along with the generic type argument (IVehicleBiulder<BlueBicycle>, BlueBicycle) to resolve the type?

Here's the rest of the code for the test, as compact and readable as I could make it.

public interface IVehicleBuilderFactory<T, TVehicle> 
    where T : IVehicleBuilder<TVehicle> where TVehicle : IVehicle
{
    T Create<T>(string country);
}

VehicleBuilder implementations

public interface IVehicleBuilder<T> where T : IVehicle { T Build(string localizedColor); }

abstract class BicycleBuilder<T> : IVehicleBuilder<T> where T : Bicycle
{
    public abstract T Build(string localizedColor);
}

public abstract class RedBicycleBuilder : IVehicleBuilder<RedBicycle>
{
    private readonly RedBicycle _Bicycle;
    public RedBicycleBuilder(RedBicycle Bicycle) { _Bicycle = Bicycle; }
    public RedBicycle Build(string localizedColor)
    {
        _Bicycle.Color = localizedColor;
        return _Bicycle;
    }
}
public abstract class GreenBicycleBuilder : IVehicleBuilder<GreenBicycle>
{
    private readonly GreenBicycle _Bicycle;
    public GreenBicycleBuilder(GreenBicycle Bicycle) { _Bicycle = Bicycle; }
    public GreenBicycle Build(string localizedColor)
    {
        _Bicycle.Color = localizedColor;
        return _Bicycle;
    }
}
public abstract class BlueBicycleBuilder : IVehicleBuilder<BlueBicycle>
{
    private readonly BlueBicycle _Bicycle;
    public BlueBicycleBuilder(BlueBicycle Bicycle) { _Bicycle = Bicycle; }
    public BlueBicycle Build(string localizedColor)
    {
        _Bicycle.Color = localizedColor;
        return _Bicycle;
    }
}

public class RedBicycleBuilder_USA : RedBicycleBuilder {
    public RedBicycleBuilder_USA(RedBicycle Bicycle) : base(Bicycle) { }
}

public class RedBicycleBuilder_Germany : RedBicycleBuilder {
    public RedBicycleBuilder_Germany(RedBicycle Bicycle) : base(Bicycle) { }
}
public class RedBicycleBuilder_France : RedBicycleBuilder {
    public RedBicycleBuilder_France(RedBicycle Bicycle) : base(Bicycle) { }
}
public class RedBicycleBuilder_Default : RedBicycleBuilder {
    public RedBicycleBuilder_Default(RedBicycle Bicycle) : base(Bicycle) { }
}

public class GreenBicycleBuilder_USA : GreenBicycleBuilder {
    public GreenBicycleBuilder_USA(GreenBicycle Bicycle) : base(Bicycle) { }
}
public class GreenBicycleBuilder_Germany : GreenBicycleBuilder {
    public GreenBicycleBuilder_Germany(GreenBicycle Bicycle) : base(Bicycle) { }
}
public class GreenBicycleBuilder_France : GreenBicycleBuilder {
    public GreenBicycleBuilder_France(GreenBicycle Bicycle) : base(Bicycle) { }
}
public class GreenBicycleBuilder_Default : GreenBicycleBuilder {
    public GreenBicycleBuilder_Default(GreenBicycle Bicycle) : base(Bicycle) { }
}

public class BlueBicycleBuilder_USA : BlueBicycleBuilder
{
    public BlueBicycleBuilder_USA(BlueBicycle Bicycle) : base(Bicycle) { }
}
public class BlueBicycleBuilder_Germany : BlueBicycleBuilder {
    public BlueBicycleBuilder_Germany(BlueBicycle Bicycle) : base(Bicycle) { }
}
public class BlueBicycleBuilder_France : BlueBicycleBuilder
{
    public BlueBicycleBuilder_France(BlueBicycle Bicycle) : base(Bicycle) { }
}
public class BlueBicycleBuilder_Default : BlueBicycleBuilder
{
    public BlueBicycleBuilder_Default(BlueBicycle Bicycle) : base(Bicycle) { }
}

Vehicle implementations:

public interface IVehicle { string Color { get; set; } }
public abstract class Vehicle : IVehicle { public string Color { get; set; } }
public abstract class Bicycle : Vehicle { }
public class RedBicycle : Bicycle { }
public class GreenBicycle : Bicycle { }
public class BlueBicycle : Bicycle { }
1

There are 1 best solutions below

4
On

Based on comments from @LukeN, I've refactored the Bicycle class, so that its color is set through constructor injection with an IColorSetter. The IColorSetter implementation has a generic Color type, and each of the Color implementations are 'localized' by way of constructor injection with an IColorLocalizer<T>.

This way, no class seems to have knowledge of anything beyond what is logically its responsibility (I think).

However, I'll need to think about this more to see how the refactored classes shown below can be used to show how to use a Ninject custom instance provider could be used to pick the property IColorLocalizer<T> now, since it's the only class that will know about colors and languages; the color coming from its generic type, and the language coming from the name of the implementation itself.

Since asking the original post, I've moved away from using an IoC container to make choices like this, choosing instead to programmatically put in code a switch for picking an implementation, with a default implementation selected for any unhandled outlier cases. But I'm not sure if it's mainly to get beyond something that's stumped me, or because it's a poor choice to lean on an IoC container in this way.

I'll need to update this answer more as I think about it.

Vehicles

public abstract class Vehicle {
    public abstract string Color { get; internal set; }
    public abstract string Move();
}

public class Bicycle : Vehicle {
    public Bicycle(IColorSetter colorSetter) { colorSetter.SetColor(this); }
    public override string Color { get; internal set; }
    public override string Move() { return "Pedaling!"; }
}

Color setters

public interface IColorSetter { void SetColor(Vehicle vehicle); }

public class ColorSetter<T> : IColorSetter where T : Color
{
    private readonly T _color;
    public ColorSetter(T color) { _color = color; }

    public void SetColor(Vehicle vehicle) { vehicle.Color = _color.Name; }
}

Color localizers

public interface IColorLocalizer<in T> where T : Color {
    void LocalizeColor(T color);
}

public class GermanBlueLocalizer : IColorLocalizer<Blue> {
    public void LocalizeColor(Blue color) { color.Name = "blau"; }
}

public class EnglishBlueLocalizer : IColorLocalizer<Blue> {
    public void LocalizeColor(Blue color) { color.Name = "blue"; }
}

Colors

public abstract class Color { public string Name { get; internal set; } }

public class Red : Color {
    public Red(IColorLocalizer<Red> colorLocalizer) {
        colorLocalizer.LocalizeColor(this); }
}

public class Green : Color {
    public Green(IColorLocalizer<Green> colorLocalizer) {
        colorLocalizer.LocalizeColor(this); }
}

public class Blue : Color {
    public Blue(IColorLocalizer<Blue> colorLocalizer) {
        colorLocalizer.LocalizeColor(this); }
}