9

I would like to be able to use ninject to inject all instances of a particular generic type into a class. For example I have a bunch of custom extractors of a format similar to:

public interface IExtract<TEntity> 
{ 
    TEntity ExtractFrom(MyBulkExportedEntity exportedEntity);
}

and I want to inject all instances of these extractors into a class responsible for processing this file using ninject multiple binding.

ie

public class ProcessDataExtract
{
    /*This isn't valid c# but demonstrates the intent of what i would like to do*/
    public ProcessDataExtract(IEnumerable<IExtract<>> allExtractors)
    {
    }

    public void Process(MyBulkExportedEntity exportedEntity)
    {
        /*loop through all of the extractors and pull relevant data from the object*/
    }
}

In the past i have done this by having a management class (IProvideExtractors) which accesses the kernel directly but i don't like this method and was wondering if anyone knows of a better way to do this. With ninject multiple binding I can then get all of the instances im interested in using kernel.GetAll(typeof(IExtract<>))

undefined
  • 33,537
  • 22
  • 129
  • 198
  • 1
    Inside the `Process` method is it needed for the `IExtract` to be generic? Because if not then I would create a non generic `IExtract` and `IExtract` would inherit from `IExtract`. And with the proper registration in your `ProcessDataExtract` constructor you would depend on `IEnumerable allExtractors` – nemesv Jun 07 '12 at 20:54

3 Answers3

4

I was looking for something related: I don't wanted to specify all the bindings separately using the Convention extension.

First: You need to inject List<IExtract> and inherit IExtract<T> : IExtract . This is simply due to the fact, that in C# you can not specify the type of a collection containing different generics. As you noted in your question it is invalid syntax - for a good reason beyond this answer.

You can later pull the elements of IExtract out of the list and use reflection to get the generic type paramer and cast it back. Or if you know for what extractor you are looking:

public IExtract<T> GetExtractor<T>() {
    return (IExtract<T>)Extractors.Find(e => e is ExtractImpl<T>);
}

Now you might have a bunch of classes for that you want some T bound to IExtract`.

Bind<IExtract>().To<ExtractImpl<MyEntity>>();
Bind<IExtract>().To<ExtractImpl<YourEntity>>();

where

MyEntity : BaseEntity
YourEntity : BaseEntity

You can specify a Convention as following

Kernel.Bind(x => x.FromThisAssembly().SelectAllClasses()
    .InheritedFrom<BaseEntity>()
    .BindWith(new GenericArgumentBindingGenerator(typeof(IExtract<>))));

Where GenericArgumentBindingGenerator is defind as:

public class GenericArgumentBindingGenerator : IBindingGenerator
{
    private readonly Type m_Generic;

    public GenericArgumentBindingGenerator(Type generic)
    {
        if (!generic.IsGenericTypeDefinition)
        {
            throw new ArgumentException("given type must be a generic type definition.", "generic");
        }
        m_Generic = generic;
    }

    public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(Type type, IBindingRoot bindingRoot)
    {
        if (type == null)
            throw new ArgumentNullException("type");
        if (bindingRoot == null)
            throw new ArgumentNullException("bindingRoot");
        if (type.IsAbstract || type.IsInterface)
        {
            return Enumerable.Empty<IBindingWhenInNamedWithOrOnSyntax<object>>();
        }

        var bindings = new List<IBindingWhenInNamedWithOrOnSyntax<object>>();
        IBindingWhenInNamedWithOrOnSyntax<object> binding = bindingRoot
            .Bind(typeof(IExtract)) // you maybe want to pass typeof(IExtract) to constructor
            .To(m_Generic.MakeGenericType(type));

        bindings.Add(binding);

        return bindings;
    }
}
Tarion
  • 16,283
  • 13
  • 71
  • 107
  • I really like this binding generator it seems pretty cool, also i think you are right extending off a non-generic would solve this issue in terms of binding – undefined Dec 17 '13 at 00:02
  • What is IEnvironmentConfigFile to be replaced it? – chad.mellor Dec 17 '13 at 01:18
  • Oh damn I missed one - got it from my code ;) IEnvironmentConfigFile would be IExtract I guess. But it should be passed through the constructor of GenericArgumentBindingGenerator to make it generic. you acutally want to bind `Bind().To>();` as mentioned above. – Tarion Dec 17 '13 at 16:49
3

The answer to this seems to be that there isnt a way of doing this with ninject

undefined
  • 33,537
  • 22
  • 129
  • 198
  • There are some workarounds in [Inject Array of Interfaces in Ninject](http://stackoverflow.com/questions/3102940/inject-array-of-interfaces-in-ninject). – ladenedge Oct 26 '12 at 17:35
1

Option A

I'm pretty sure you can do this:

public class ProcessDataExtract
{
    public ProcessDataExtract<TExtract>(IEnumerable<IExtract<TExtract>> allExtractors)
    {
    }
    ...etc...
}

And then list out your bindings in your binding module's Load method:

...
Bind<IExtract<TEntity>>().To<SomeConcreteExtract>();
Bind<IExtract<TEntity>>().To<AnotherConcreteExtract>();
Bind<IExtract<TEntity>>().To<YetAnotherConcreteExtract>();
...

And NInject will deliver them to your constructor that advertises a dependency on a bunch of them. I've done that in the past with success.

Option B

Change

public interface IExtract<TEntity> 
{ 
    TEntity ExtractFrom(MyBulkExportedEntity exportedEntity);
}

to

public interface IExtract
{ 
    TEntity ExtractFrom<TEntity>(MyBulkExportedEntity exportedEntity);
}

Which would allow:

        public ProcessDataExtract<TExtract>(IEnumerable<IExtract<TExtract>> allExtractors)
    {
    }
    ...etc...

to be:

    public ProcessDataExtract(IEnumerable<IExtract> allExtractors)
    {
    }
    ...etc...

And the NInject bindings would be adjusted, too:

...
Bind<IExtract>().To<SomeConcreteExtract>();
Bind<IExtract>().To<AnotherConcreteExtract>();
Bind<IExtract>().To<YetAnotherConcreteExtract>();
...
bluevector
  • 3,485
  • 1
  • 15
  • 18
  • Yeah so in ninject there is a concept of injecting all instances of a binding which is what i would like to leverage. You can do this directly from ninject with the above instance using `kernel.GetAll(typeof(IExtract<>))` which will return an `IEnumerable` containing all of my extractors. My problem isnt with this, my problem is that i cant work out how to specify this in my constructor, as the above code isnt valid C# – undefined Jun 07 '12 at 20:45
  • I corrected the syntax. You'll be stuck with just one conrete `TExtract`, however. Which would need to be a non-generic base class (abstract or not). Or you could push the type parameter from `IExtract` to the `ExtractFrom`, which would take the homogeneity away from `ProcessDataExtract`. I'll add what I mean into my answer. – bluevector Jun 07 '12 at 20:56
  • I suppose what your saying is I could use `Enumerable> allExtractors`, im pretty sure this isnt possible as i dont think `typeof(IExtract).IsAssignableFrom(tyepof(IExtract))` see https://compilify.net/1tj – undefined Jun 07 '12 at 21:00
  • To make your reflection life easier, use a naming convention for the concrete `IExtract` classes that encodes the `TEntity` so that you don't have to the loop each time you add a new one. – bluevector Jun 07 '12 at 21:08
  • that seems like a bit of a hack to get around the issue though, surely there is a more elegant solution – undefined Jun 07 '12 at 21:11