Open Generics and IEnumerable with Ninject

379 Views Asked by At

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.

2

There are 2 best solutions below

12
On BEST ANSWER

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<>));
1
On

A handler FooHandler : IHandler<FooRequest> cannot be casted to IHandler<IRequest>. You won't be able to achieve this with any IoC, because the TRequest type parameter cannot be in and out at the same time.

If you want it "both ways", you'll have to bind the handlers to some generic interface, like an empty IHandler interface. Then inject all of them and by reflection find the right one and call it with an argument casted to the correct type.

The alternative is, of course, to change the signature of void Handle(IEnumerable<TFor> requests) to void Handle(IEnumerable<IRequest> requests) and let the implementation do the casting. But i suppose that would be a worse solution for your scenario.

You'll also need two distinct bindings or one multi binding like Bind<IHandler<Foo>, IHandler>().To<FooHandler>().InSingletonScope() for each type. The multi-binding is beneficial when you require scoping. If you've got two bindings for the same type with InSingletonScope, there will be two instances. If you use multi-binding, there'll be only one instance.


To make Steven's CompositeHandler work with ninject, you've got to adapt the solution slightly and introduce a new interface for the composite handler:

public class CompositeRequest<TFor> : IRequest
{
    public CompositeRequest(params TFor[] requests)
    {
        this.Requests = requests;
    }

    public TFor[] Requests { get; private set; }
}

public interface ICompositeHandler<TFor> : IHandler<CompositeRequest<TFor>> { }

public class CompositeHandler<TFor> : ICompositeHandler<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);
        }
    }
}

then create the binding as follows:

        var kernel = new StandardKernel();

        kernel.Bind(typeof(ICompositeHandler<>)).To(typeof(CompositeHandler<>));

        kernel.Bind(x => x.FromThisAssembly()
            .SelectAllClasses()
            .InheritedFrom(typeof(IHandler<>))
            .Excluding(typeof(CompositeHandler<>))
            .BindDefaultInterfaces());

        kernel.Get<ICompositeHandler<Foo>>();

I've verified it to work.