2

I'm building on a previously answered question in which ICar implementations are bound using Ninject Conventions Extensions and a custom IBindingGenerator, and the ICarFactory interface is bound using the Ninject Factory Extensions' ToFactory() method and a custom instance provider.

I'm trying to refactor so that I can bind and make use of a IVehicleFactory<T>, where T is constrained to ICar, rather than the previous ICarFactory. This way, I can specify the vehicle I want in the generic type parameter, instead of passing in the name of the vehicle type in the factory's CreateCar() method.

Is it possible to bind open generic interfaces using the ToFactory() technique?

I have a feeling that I'm barking up the wrong tree, but when I was specifying an ICar type by its name, it seemed like the natural evolution to specify the ICar type itself as a generic type parameter...

Here's the test that currently fails:

[Fact]
public void A_Generic_Vehicle_Factory_Creates_A_Car_Whose_Type_Equals_Factory_Method_Generic_Type_Argument()
{
    using (StandardKernel kernel = new StandardKernel())
    {
        // arrange
        kernel.Bind(typeof(IVehicleFactory<>))
            .ToFactory(() => new UseFirstGenericTypeArgumentInstanceProvider());

        kernel.Bind(
            scanner => scanner
                           .FromThisAssembly()
                           .SelectAllClasses()
                           .InheritedFrom<ICar>()
                           .BindWith(new BaseTypeBindingGenerator<ICar>()));
        IVehicleFactory<Mercedes> factory 
            = kernel.Get<IVehicleFactory<Mercedes>>();

        // act
        var car = factory.CreateVehicle();

        // assert
        Assert.IsType<Mercedes>(car);
    }
}

And the InvalidCastException thrown:

System.InvalidCastException was unhandled by user code
  Message=Unable to cast object of type 'Castle.Proxies.ObjectProxy' to type 'IVehicleFactory`1[Mercedes]'.
  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 NinjectFactoryTests.A_Generic_Vehicle_Factory_Creates_A_Car_Whose_Type_Name_Equals_Factory_Method_String_Argument() in C:\Programming\Ninject.Extensions.Conventions.Tests\NinjectFactoryTests.cs:line 37
  InnerException: 

And the factory interface:

public interface IVehicleFactory<T> where T : ICar
{
    T CreateVehicle();
}

And the custom instance provider, whose breakpoints I can't even get the debugger to stop on, so I really don't know what's going on in there:

public class UseFirstGenericTypeArgumentInstanceProvider : StandardInstanceProvider
{
    protected override string GetName(MethodInfo methodInfo, object[] arguments)
    {
        var genericTypeArguments = methodInfo.GetGenericArguments();
        var genericMethodDefinition = methodInfo.GetGenericMethodDefinition();
        var g = genericMethodDefinition.MakeGenericMethod(genericTypeArguments.First());
        return g.MemberType.GetType().Name;
    }

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

EDIT 1 - Change IVehicleFactory signature and custom instance provider

Here's I've changed the IVehicleFactory signature to use a generic Create<T>() method, and explicitly bound Mercedes to itself.

public interface IVehicleFactory
{
    T CreateVehicle<T>() where T : ICar;
}

And the new custom instance provider which returns the name of the first generic type parameter:

public class UseFirstGenericTypeArgumentInstanceProvider : StandardInstanceProvider
{
    protected override string GetName(MethodInfo methodInfo, object[] arguments)
    {
        var genericTypeArguments = methodInfo.GetGenericArguments();
        return genericTypeArguments[0].Name;
    }
}

Here's the new test, still not passing:

[Fact]
public void A_Generic_Vehicle_Factory_Creates_A_Car_Whose_Type_Name_Equals_Factory_Method_String_Argument()
{
    using (StandardKernel kernel = new StandardKernel())
    {
        // arrange
        kernel.Bind<IVehicleFactory>()
            .ToFactory(() => new UseFirstGenericTypeArgumentInstanceProvider())
            .InSingletonScope();
        kernel.Bind<Mercedes>().ToSelf();
        IVehicleFactory factory = kernel.Get<IVehicleFactory>();

        // act
        var car = factory.CreateVehicle<Mercedes>();

        // assert
        Assert.IsType<Mercedes>(car);
    }
}

}

A Ninject.ActivationException is thrown:

Ninject.ActivationException: Error activating Mercedes
No matching bindings are available, and the type is not self-bindable.
Activation path:
  1) Request for Mercedes

I don't know why it can't find the Mercedes class, since I explicitly self-bound it. Can you spot what I'm doing wrong?

Community
  • 1
  • 1
Jeff
  • 2,191
  • 4
  • 30
  • 49

1 Answers1

2

Use generic methods:

public interface IVehicleFactory
{
    CreateVehicle<T>();
}
Remo Gloor
  • 32,665
  • 4
  • 68
  • 98
  • Ok, so this [wouldn't really be an abstract factory](http://blog.ploeh.dk/2010/11/01/PatternRecognitionAbstractFactoryorServiceLocator/) then, because the generic argument is in the method rather than in the factory? Or is it still an abstract factory because the kernel is the abstract factory? Just trying to understand whether your solution is correct because my design is flawed, or simply because this is how it's commonly done in this scenario? – Jeff Apr 09 '13 at 16:58
  • Please see my edit with your suggestion - the test is not yet passing, because it cannot resolve the specific `ICar` class I'm supplying to `CreateVehicle`, which is `Mercedes`, in this case. I don't know why, since I even explicitly self-bound it. Can you see what I'm doing wrong? It doesn't seem that it was as simple as switching to a generic method. – Jeff Apr 09 '13 at 20:27
  • Your UseFirstGenericTypeArgumentInstanceProvider enforces that bindings are named. Either remove it of add .Named("Mercedes") to your binding. – Remo Gloor Apr 09 '13 at 22:08
  • I see - is it just my implementation of the custom instance provider that's enforcing named bindings, or does the use of any implementation of the StandardInstanceProvider enforce named bindings? What path do I pursue if I want to bind using other than named bindings? I want to set up something that I can use with convention-based bindings ultimately, so adding `.Named("Mercedes")` isn't practical. – Jeff Apr 09 '13 at 23:50
  • https://github.com/ninject/ninject.extensions.factory/wiki/Factory-interface%3A-Referencing-Named-Bindings – Remo Gloor Apr 10 '13 at 05:34
  • It was helpful to read the wiki again, although I'm not sure if there was something specific there you're pointing to... The example customization shown there is for modifying named binding behavior, so does this mean that using the `ToFactory()` method implies the use of named bindings is in use? In other words, no matter what customization I make, somewhere a string will be have to be composed spelling out the name of the type I want bound? – Jeff Apr 10 '13 at 06:02
  • All your questions are already in the documentation. Here it is even better described (See last part): https://github.com/ninject/ninject.extensions.factory/wiki/Factory-interface – Remo Gloor Apr 10 '13 at 10:23