Container adapters

Mogens Heller Grabe edited this page Jul 30, 2015 · 3 revisions

Unless you're working with Rebus in one-way client mode (as described on Different bus modes), Rebus will be receiving messages and will at some point need to obtain a message handler instance to handle a message.

Rebus will do that by asking the IHandlerActivator abstraction for handlers for the incoming message, and this is usually where you would use an implementation of that wraps an IoC container, a so-called "container adapter", to create the handler instances by delegating the instantiation to the IoC container.

This way, handlers can have dependencies injected, instance lifetimes managed, etc.

In order to configure Rebus to use a container adapter, include the NuGet package of the container of your choice (e.g. Rebus.Castle.Windsor) and go

Configure.With(new CastleWindsorContainerAdapter(container))
    .(...)

This will do the following two things:

  1. Pass the container adapter to Rebus, thus delegating handler instantiation to the container.
  2. Put the resulting IBus instance into the container with a singleton lifestyle, ensuring that the bus is disposed when the container is disposed.

When your application shuts down

When your application shuts down, you would have done this anyway:

container.Dispose();

Just keep doing it, because the container adapter ensures that the IBus instance is registered in a way that disposes the bus properly when the container is disposed, allowing currently executing handlers to finish gracefully, etc.

The Rebus philosophy

At some point, Rebus had a registration API for automatically picking up and registering handlers in the container, but it was extremely hard to normalize the behavior across all the available IoC containers. Therefore: you're on your own when it comes to registering handlers.

It shouldn't be too hard, though - I'm not an expert on all IoC containers, but with Castle Windsor you'd usually be satisfied doing something like this:

container.Register(Classes.FromAssemblyContaining<SomeHandler>()
                      .BasedOn<IHandleMessages>()
                      .WithServiceAllInterfaces()
                      .LifestyleTransient());

in order to automagically pick up all Rebus handlers from one particular assembly.

A word of warning, though

When you're working with sagas and you have multiple worker threads, it is important that your handlers have a transient lifestyle! In other words, it is important that every incoming message will result in a fresh handler instance.

This is because of the Data property on the handler - this is where the current saga instance will be put, and if your handler instance is a singleton it will be shared between threads, most likely leading to the hardest-to-track-down bugs you'll ever not track down.

You know what? Just forget about the "when this and when that" I said above - just make sure, always, that your handlers are registered with a transient lifestyle! - in general, transient lifestyle is a better default for IoC container components, and that is a fact.

The built-in container adapter

Since you may not need a full-blown IoC container, and since you may be wishing for a more lightweight way of handling messages, Rebus has a built-in container adapter.

It's not an IoC container, so it's a little different - one aspect that's different, is that it implements IDisposable - this means that the adapter can be disposed, which you should do when your application shuts down.

You can dispose the IBus too - basically, just make sure you either dispose the IBus or the BuiltinHandlerActivator.

Here's an example on how it can be used:

using(var activator = new BuiltinHandlerActivator())
{
    Configure.With(activator)
        .Transport(t => t.UseMsmq("inputQueue"))
        .Start();

    // here's the bus
    var bus = activator.Bus;

    // publish greetings forever
    while(true)
    {
        bus.Publish(Console.ReadLine()).Wait();
    }
}

If you want to handle messages with BuiltinHandlerActivator, you have a couple of options - first, you can register a handler factory:

adapter.Register(() => new SomeHandler(pass, dependencies, into, ctor));

The Register method will reflect on the inferred handler type and figure out which messages it can handler.

Lastly, there's the inline handler:

adapter.Handle<SomeMessage>(async msg => Console.WriteLine("Got message: {0}", msg.Text));

which can be neat to use in simple scenarios. If you need the bus and/or the message context with the inline handler, there' two overloads for that:

adapter.Handle<SomeRequest>(async (bus, msg) => await bus.Reply(new SomeReply()));

adapter.Handle<SomeRequest>(async (bus, context, msg) => {
    var headers = context.TransportMessage.Headers;
    var returnAddress = headers[Headers.ReturnAddress];
    await bus.Reply(new SomeReply(string.Format("hello {0}", returnAddress)));
});
Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.