Search code examples
c#ninjectopen-generics

Open Generics and IEnumerable with Ninject


I have the following interface...

public interface IHandler<in TFor> where TFor : IRequest
{
    void Handle(IEnumerable<TFor> requests);
}

which is typically implemented like so...

public class AssignmentHandler : HandlerBase, IHandler<AssignmentRequest>
{
    public void Handle(IEnumerable<AssignmentRequest> assigmentRequests)
    {
        foreach(var request in assignmentRequests).....
    }
}

IRequest is simply a marker interface (at this point).

I register all handlers via conventions with the following...

public override void Load()
    {

        Kernel.Bind(x => x.FromAssemblyContaining<IHandler>()
            .SelectAllClasses()
            .InheritedFrom(typeof(IHandler<>))
            .BindAllInterfaces());

        //i also tried...
        Kernel.Bind(x => x.FromAssemblyContaining<IHandler>()
            .SelectAllClasses()
            .InheritedFrom<IHandler<IRequest>>()
            .BindSingleInterface());

    }

and individually they resolve just fine.

There is one situation where i would like to resolve all handlers, and have them injected into a constructor like so...

public SomeConstructor(IEnumerable<IHandler<IRequest>> allHandlers)

This does not work and always returns empty.

My understanding is because i have registered them by convention as IHandler<ConcreteRequest>, not IHandler<IRequest> which is 2 distinct signatures.

How can i register all handlers by convention in a way that they will be identified collectively as IEnumerable<IHandler<IRequest>> while also individually?

A 2nd registration is OK, but would much prefer resolution of one implementation via both signatures.


Solution

  • Not an answer to your question, but it seems to me that you're missing an abstraction, since all your handlers contain that same foreach loop, which means you're violating DRY. I suggest changing the interface to the following:

    public interface IHandler<in TFor> where TFor : IRequest
    {
        void Handle(TFor request);
    }
    

    And have a generic implementation that allows handling multiple instances:

    public class CompositeRequest<TFor> : IRequest
    {
        public CompositeRequest(params TFor[] requests)
        {
            this.Requests = requests;
        }
    
        public TFor[] Requests { get; private set; }
    }
    
    public class CompositeHandler<TFor> : IHandler<CompositeRequest<TFor>> 
        where TFor : IRequest
    {
        private readonly IHandler<TFor> handler;
    
        public CompositeHandler(IHandler<TFor> handler)
        {
            this.handler = handler;
        }
    
        public void Handle(CompositeRequest<TFor> request)
        {
            foreach (var r in request.Requests)
            {
                this.handler.Handle(r);
            }
        }
    }
    

    This removes the need for every handler to implement the foreach loop, and if the way the list should be processed ever changes, you only have to change it in a single place.

    How to register this in Ninject, I unfortunately don't know.

    UPDATE

    With Simple Injector, the registration would simply be as follows:

    // using SimpleInjector.Extensions;
    
    // Batch register all handlers in the system.
    container.RegisterManyForOpenGeneric(typeof(IHandler<>), 
        typeof(AssignmentHandler).Assembly);
    
    // Register the open-generic CompositeHandler<TFor>
    container.RegisterOpenGeneric(typeof(IHandler<>), typeof(CompositeHandler<>));