4

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:

  • InitializerForShadeOfBlue inherits from InitializerForBlue
  • InitializerForBlue inherits from abstract Initializer<Blue>
  • abstract Initializer<T> directly implements IInitializer<T>

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!";}
}
Jeff
  • 2,191
  • 4
  • 30
  • 49

2 Answers2

6

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

Jeff
  • 2,191
  • 4
  • 30
  • 49
Remo Gloor
  • 32,665
  • 4
  • 68
  • 98
  • Ok, I'll take a stab at that and post an update. For this purpose, it it better to inherit from the AbstractInterfaceBindingGenerator or implement IBindingGenerator? I notice that the existing ones do either/or. – Jeff Mar 07 '13 at 01:18
  • @Lumirris The code (and the wiki IIRC) has the answer (in general prefer `AbstractInterfaceBindingGenerator`, unless you hit a roadblock which you cant get around by implementing the base interface OR by a pull request) – Ruben Bartelink Mar 07 '13 at 09:11
0

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

Jeff
  • 2,191
  • 4
  • 30
  • 49
  • Ninject can't do anything C# can't. 'InitializerOfShadeOfBlue' doesn't implement the 'IInitializer' interface. You have to get that right first. – Remo Gloor Mar 10 '13 at 13:31
  • @RemoGloor But, it does inherit from `InitializerOfBlue`, which in turn inherits from `Initializer`. I'm working on something that climbs up the type ancestor tree of `InitializerOfShadeBlue` until it finds `Initializer`, grabs the GenericTypeArgment of `Blue`, then examines the assembly for `Blue` subclasses to find `ShadeOfBlue`. It then uses `ShadeOfBlue` to build a generic type definition of `IInitializer`, which I can bind to `InitializerOfShadeOfBlue`.I feel like I'm pretty far down the rabbit hole right now, but I'll try to get it working and post it. – Jeff Mar 11 '13 at 01:00
  • @Lumrirris No sorry if InitializerOfShadeOfBlue does not implement IInitializer there is not way to cast it to this interface. – Remo Gloor Mar 11 '13 at 01:08
  • @RemoGloor Ok. Looks like I need to rework this design. All I'm trying to do is 1) Make `Blue` and all of its subclasses ignorant of how they're initialized, by 2) Having separate initializer classes with inheritance that mimics that of the `Blue` class hierarchy, but not place them in `Blue*` constructor signatures (to keep `Blue`, etc., ignorant of their initialization). Maybe I could find a way to select all the base color classes (`Blue`, `Red`, etc) and map them to the single implementation of `IInitializer' even though they don't take one in their constructor args... – Jeff Mar 11 '13 at 01:55