Ninject Injection of all instances of a generic type with ninject
Asked Answered
A

3

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<>))

Asthenia answered 7/6, 2012 at 20:25 Comment(1)
Inside the Process method is it needed for the IExtract<TEntity> to be generic? Because if not then I would create a non generic IExtract and IExtract<TEntity> would inherit from IExtract. And with the proper registration in your ProcessDataExtract constructor you would depend on IEnumerable<IExtract> allExtractorsCasiano
B
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;
    }
}
Ballad answered 14/12, 2013 at 11:25 Comment(3)
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 bindingAsthenia
What is IEnvironmentConfigFile to be replaced it?Puparium
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<IExtract>().To<ExtractImpl<YourEntity>>(); as mentioned above.Ballad
A
3

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

Asthenia answered 18/6, 2012 at 9:18 Comment(1)
There are some workarounds in Inject Array of Interfaces in Ninject.Sarisarid
C
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>();
...
Cockburn answered 7/6, 2012 at 20:41 Comment(5)
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<object> 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#Asthenia
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.Cockburn
I suppose what your saying is I could use Enumerable<IExtract<object>> allExtractors, im pretty sure this isnt possible as i dont think typeof(IExtract<object>).IsAssignableFrom(tyepof(IExtract<MyConcreteEntity>)) see compilify.net/1tjAsthenia
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.Cockburn
that seems like a bit of a hack to get around the issue though, surely there is a more elegant solutionAsthenia

© 2022 - 2024 — McMap. All rights reserved.