Skip to content
Mogens Heller Grabe edited this page Aug 1, 2023 · 7 revisions

Microsoft's generic host

Modern versions of .NET allow the use of Microsoft's generic hosting model, whereby most types of applications (console app, background service (Docker/Windows Service/whatever), web apps, etc) pretty much are initialized in the same way.

Anywhere Microsoft's generic host ("Microsoft Extensions Hosting") is available, you can pull in Rebus.ServiceProvider (v8 or later) and have Rebus automatically integrate with the hosting environment by configuring it like this:

services.AddRebus(
    configure => configure
        .Transport(t => t.Use(...))
);

which should probably be considered "the contemporary way" of configuring Rebus. Initializing it this way ensures that Rebus is started/stopped at the right times and will resolve message handlers from the service provider.

Btw. message handlers can be registered by going

services.AddRebusHandler<YourMessageHandler>();

Of course you might still want/need to initialize Rebus in other ways (e.g. in automated integration tests, one-off apps, or if you prefer other IoC containers), but for most scenarios, I recommend using Rebus.ServiceProvider

More about IoC containers

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:

activator.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:

activator.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:

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

activator.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