HCdev
HCdev

Reputation: 550

Ninject mapping to generic type

I have a bunch of events which are processed by event handlers such as the following:

Event Handler:

public class DeliveryEventHandlers :
IConsume<DeliveryCreated>,
IConsume<DeliveryUpdated>{

readonly IDocumentSession _documentSession;

public DeliveryEventHandlers(IDocumentSession documentSession)
{
    _documentSession = documentSession;
}

public void Consume(DeliveryCreated @event)
{
    //...
}

public void Consume(DeliveryUpdated @event)
{
    //...
}
...

Events:

public class DeliveryCreated : IEvent
{
    public Guid DeliveryId { get; set; }
    ...
}

public class DeliveryUpdated : IEvent
{
    public Guid DeliveryId { get; set; }
    ...
}

And I need to write a Ninject binding that will on request of an Event type, give me the Event handler(s) that consume those events. This is what I have come up with:

public void BindEventHandlers(IContext context) {
        Kernel.Bind(x =>
        {
            x.FromAssemblyContaining(typeof(DeliveryCreated))
            .SelectAllClasses()
            .InheritedFrom<IEvent>()
            .BindWith(new EventBindingGenerator());
        });
    }

    public class EventBindingGenerator : IBindingGenerator
    {
        public IEnumerable<IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(Type type, IBindingRoot bindingRoot)
        {
            yield return bindingRoot.Bind(x =>
            {
                x.FromAssemblyContaining(typeof(DeliveryEventHandlers))
                .SelectAllClasses()
                .InheritedFrom(IConsume<typeof(type)>);
                // This Part ^
            });
        }
    }

but this will not actually compile - I have hit a snag on the line above the comment: // This Part ^

I need to do a query for:

_context.Get<DeliveryCreated>() 

and recieive a DeliveryCreatedEventHandler.

Any help would be muchos appreciated!!

Thanks, H

Upvotes: 1

Views: 318

Answers (2)

BatteryBackupUnit
BatteryBackupUnit

Reputation: 13233

This can actually be solved quite easily with the conventions extension functionality:

kernel.Bind(x => x.FromAssemblyContaining(typeof(DeliveryCreated))
    .IncludingNonePublicTypes() // may not be needed in your case
    .SelectAllClasses()
    .InheritedFrom(typeof(IConsume<>))
    .BindAllInterfaces());

the following test succeeds (syntax is from FluentAssertions):

kernel.Get<IConsume<DeliverCreated>>().Should().BeOfType<DeliveryEventHandlers>();
kernel.Get<IConsume<DeliveryUpdated>>().Should().BeOfType<DeliveryEventHandlers>();

Alternatively, if you want to make sure you don't want to bind the IConsume<...> implementations to more types than necessary, you can replace the BindAllInterfaces statement as follows:

private static IEnumerable<Type> SelectConsumeInterfacesOnly(
    Type type, IEnumerable<Type> baseTypes)
{
    var matchingTypes = baseTypes.Where(t => 
         t.IsGenericType
         && t.GetGenericTypeDefinition() == typeof (IConsume<>));
    return matchingTypes;
}

kernel.Bind(x => x.FromThisAssembly()
    .IncludingNonePublicTypes()
    .SelectAllClasses()
    .InheritedFrom(typeof(IConsume<>))
    .BindSelection(SelectConsumeInterfacesOnly));

Again, I've verified that it actually works.

Upvotes: 1

Yacoub Massad
Yacoub Massad

Reputation: 27861

You can manually scan the assembly and register all classes that implement the IConsumer<> interface. Like this:

foreach (Type type in assembly.GetTypes().Where(x => x.IsClass))
{
    foreach (
        var @interface in
            type.GetInterfaces()
                .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IConsume<>)))
    {
        kernel.Bind(@interface).To(type);
    }
}

If you know that there will be a single consumer of the event, use the following:

var single_consumer = kernel.Get<IConsume<DeliveryCreated>>();

In case that there can be multiple consumers, use the following to obtain all of them:

var all_consumers = kernel.GetAll<IConsume<DeliveryCreated>>();

Upvotes: 0

Related Questions