Jeff
Jeff

Reputation: 2281

How do I bind generic types with inheritance using Ninject Conventions extensions

How can I bind InitializerForXXX (non-generic implementation) to IInitializer<XXX> (generic interface) using Ninject Conventions so that requests for an IInitializer<T> resolve a non-generic implementation whose name starts with InitializerFor and end with typeof(T).Name like:

initializerFactory.CreateFor<Blue>();        //resolves InitializerOfBlue
initializerFactory.CreateFor<ShadeOfBlue>(); //resolves InitializerOfShadeOfBlue

where no non-abstract class directly implement IInitializer<T>, and some implementations inherit from other implementations:

I'm hoping I can use a .EndsWith(typeof(T).Name) for a given IInitializer<T> convention I can use, because there are literally hundreds of initializers in the ShadeOfxxx vein. If I have to map all of them, I'm better off finding a way to resolve with reflection at runtime.

Given the following:

UPDATE: bindings with custom binding generator (see my answer below for implementation)

    void Bootstrap(IBindingRoot kernel)
    {
        kernel.Bind<IInitializerFactory>()
            .To<InitializerFactory>()
            .InSingletonScope();

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

        kernel.Bind(scanner =>
                    scanner.FromThisAssembly().SelectAllClasses()
                        .WhichAreNotGeneric()
                        .InheritedFrom(typeof(IInitializer<>))
                        .BindWith<FirstTypeParameterNameMatchesEndOfBoundClassNameGenerator>());
    }

main method

void Main(IEnumerable<string> values)
{
    // setup bindings
    var kernel = new StandardKernel();
    Bootstrap(kernel);

    IInitializerFactory initializerFactory = 
        kernel.Get<IInitializerFactory>();

    IInitializer<ShadeOfBlueComplexContent> initializer = 
        initializerFactory.CreateFor<ShadeOfBlueComplexContent>();

    initializer.Initialize(values);
}

initializer factory

interface IInitializerFactory
{
    IInitializer<T> CreateFor<T>() where T : class, IComplexContent, new();
}

class InitializerFactory : IInitializerFactory
{
    public IInitializer<T> CreateFor<T>() where T : class, IComplexContent, new()
    {
        return MagicallyGetInitializer<T>();
    }

    //behind the curtain, whirring noises are heard as 't' is resolved...
    private static IInitializer<T> MagicallyGetInitializer<T>() 
        where T : class, IComplexContent, new()
    {
        IInitializer<T> i = null;
        return i;
    }
}

initializers

interface IInitializer<out T> where T : IComplexContent
{
    T Initialize(IEnumerable<string> values);
}

abstract class Initializer<T> : IInitializer<T> where T : IComplexContent
{
    public abstract T Initialize(IEnumerable<string> values);
}

class InitializerOfBlue : Initializer<Blue>
{
    private readonly Blue _content;

    public InitializerOfBlue(Blue content) {_content = content;}

    public override Blue Initialize(IEnumerable<string> values)
    {
        _content.BlueSpecificProperty = values.ElementAt(0);
        //... populate other blue-specific properties like this
        return _content;
    }
}

class InitializerOfShadeOfBlue : InitializerOfBlue
{
    public InitializerOfShadeOfBlue(ShadeOfBlue content) : base(content){}
}

content models

interface IComplexContent
{
    string OneBasicProperty { get; set; }
    // other properties are specific to implementation
    string UniqueOperation();
}

abstract class BaseComplexContent : IComplexContent
{
    public string OneBasicProperty { get; set; }
    public abstract string UniqueOperation();
}

class Blue : BaseComplexContent
{
    // initializer sets this
    public string PropertyForAllKindsOfBlue { get; set; }

    // initializer doesn't interact with this
    public override string UniqueOperation() {return "I'm plain.";}
}

class ShadeOfBlue : Blue
{
    // initializer doesn't interact with this
    public override string UniqueOperation() {return "I'm fabulous!";}
}

Upvotes: 4

Views: 3168

Answers (2)

Remo Gloor
Remo Gloor

Reputation: 32725

You are over specifying the class selection

    kernel.Bind(scanner =>
                scanner.FromThisAssembly().SelectAllClasses()
                    .WhichAreNotGeneric()
                    .InheritedFrom(typeof (IInitializer<>))

This is already enough. What you need to do though is to add a custom Binding Generator. That selects IInitializer<Blue> for InitializerForBlue and IInitializer<ShadeOfBlue> for InitializerForShadeOfBlue

https://github.com/ninject/ninject.extensions.conventions/wiki/Projecting-Services-to-Bind

Upvotes: 6

Jeff
Jeff

Reputation: 2281

BEGIN SOLUTION CANDIDATE - custom binding generator:

custom binding generator

Thanks for the advice, @RemoGloor and @RubenBartelink. I'm stumped though - the problem is that I wind up binding the IInitializer<Blue> to InitializerOfShadeOfBlue. I need to be able to somehow change the generic type argument from Blue to ShadeOfBlue in the IInitializer<Blue> binding candidate, since IInitializer<ShadeOfBlue> is what will be requested from the factory method at runtime.

Is there a way to modify the generic type argument list of the binding candidate? Or am I barking up the wrong implementation? Any edit suggestions to my OP or this answer are appreciated.

/// <summary>Creates bindings on open generic types where bound implementations'
/// names end  with the name of the generic type argument</summary>
public class FirstTypeParameterNameMatchesEndOfBoundClassNameGenerator : IBindingGenerator
{
    public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(Type type, IBindingRoot bindingRoot)
    {
        if (type == null) throw new ArgumentNullException("type");
        if (bindingRoot == null) throw new ArgumentNullException("bindingRoot");

        // only consider concrete, non-abstract classes
        if (type.IsInterface || type.IsAbstract) yield break;

        var bindingType = GetBindingType(type);

        if (bindingType != null)
            yield return bindingRoot.Bind(bindingType).To(type);
        // ARGH! bindingType == IInitializer`1[[Blue]] but I want
        // IInitializer`1[[ShadeOfBlue]] for type == ShadeOfBlue

    }

    private static Type GetBindingType(Type type)
    {
        Type goodMatch = null;

        foreach (var candidate in type.GetInterfaces())
        {
            // skip non-generic interfaces
            if (!candidate.IsGenericType) continue;

            // assumption: using argument in first position
            var firstArg = candidate.GetGenericArguments().First();
            if (!type.Name.EndsWith(firstArg.Name)) continue;

            // IInitializer<XXX> matches InitializerOfXXX
            goodMatch = candidate;
            break;
        }
        if (goodMatch == null)
        {
            // if no match on interfaces, walk through the ancestor types
            foreach (var candidate in type.GetAllAncestors())
            {
                goodMatch = GetBindingType(candidate);
                if (goodMatch != null) break;
            }
        }
        return goodMatch;
    }

Type Extension helper

public static class TypeExtensions
{
    // returns all ancestor types starting with the parent
    public static IEnumerable<Type> GetAllAncestors(this Type type)
    {
        for (var current = type.BaseType; current != null; current = current.BaseType)
            yield return current;
    }
}

END SOLUTION CANDIDATE - custom binding generator

Upvotes: 0

Related Questions