2

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.

Baldy
  • 3,621
  • 4
  • 38
  • 60
  • a handler `FooHandler : IHandler` cannot be casted to `IHandler`. 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.. – BatteryBackupUnit Jul 28 '14 at 12:15
  • Why you are using `TFor` generic type when you can directly use `IRequest` interface? – HuorSwords Jul 28 '14 at 12:16
  • Because each request type has its own handler type. – Baldy Jul 28 '14 at 13:58

2 Answers2

1

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.

BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
  • yep, changing the signature as suggested involves the casting, which just feels messy. A well described summary of the situation nonetheless :) – Baldy Jul 28 '14 at 13:57
1

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<>));
Steven
  • 166,672
  • 24
  • 332
  • 435
  • Steven, how would you register this in SimpleInjector? it may present a compelling reason to switch :) – Baldy Jul 28 '14 at 13:56
  • 1
    @Baldy: I try to prevent promoting Simple Injector when answering questions for other DI containers, but since you explicitly asked: please see my update :-). – Steven Jul 28 '14 at 14:01
  • 1
    For ninject the open generic binding is done like this: `IBindingRoot.Bind(typeof(IHandler<>)).To(typeof(MultipleHandler<>));` – BatteryBackupUnit Jul 28 '14 at 14:08
  • 1
    @BatteryBackupUnit: Have you tried that? That doesn't work. Ninject throws a "cyclical dependency was detected" exception when you try to resolve the composite handler. – Steven Jul 28 '14 at 14:50
  • @Baldy: I updated my answer. The code didn't compile because I forgot about the `where TFor : IRequest` type constraint. And do note that BatteryBackupUnit's suggestion looks good, but doesn't work unfortunately. – Steven Jul 28 '14 at 15:16
  • @BatteryBackupUnit: Sure, but in this case there actually is no cyclic dependency since the `CompositeHandler` implements `IHandler>` and it depends on `IHandle`, and since there is a binding from `IHandle` to `AssignmentHandler`, that should just work. `Bind(typeof(IHandler<>)).To(typeof(CompositeHandler<>))` does unfortunately not work, but what is the correct registration for this? – Steven Jul 28 '14 at 15:33
  • 1
    Ok sorry yes i've overlooked that part. I've updated my answer to reflect how this would need to be implemented using ninject. Ninject does indeed not support a notation like `.Bind(typeof(IHandler>))`. First off, the compiler does not support it. Second, you could specify it by doing `typeof(CompositeHandler).GetInterfaces().Single()`. But ninject does not support that (and - it's not very nice). So you've got to introduce an intermediate interface `ICompositeHandler : IHandler> where T : IRequest` – BatteryBackupUnit Jul 28 '14 at 15:47
  • @Steven based upon your code above, how would i get an enumerable of all IHandler registrations? container.GetAllInstances(typeof (IHandler<>)); produces the exception "No registration for type IEnumerable> could be found." – Baldy Jul 29 '14 at 09:47
  • @Baldy: Simple Injector explicitly separates registration of collections from 'normal' registrations. But why do you want to resolve all implementations? How are you using them? Perhaps it's good to start a new Stackoverflow question for this. – Steven Jul 29 '14 at 11:08
  • yes, the more time i spend on this, the more i see a fundamental design issue. That would certainly warrant its own question. – Baldy Jul 29 '14 at 12:04
  • @Baldy: It's absolutely possible (and quite easy) to get all `IHandler` registrations, but I bet we can come up with a better design. – Steven Jul 29 '14 at 12:07
  • @Steven a question on the design... https://stackoverflow.com/questions/25017370/resolve-handlers-using-factory-via-ioc-container – Baldy Jul 29 '14 at 14:07