Provides an Microsoft.Extensions.DependencyInjection-based container adapter for Rebus.
This container adapter is meant to be used with the generic host introduced with .NET Core 2.1, which has evolved into the ubiquitous hosting model for .NET.
When you configure your services, do this to invoke the Rebus configuration spell:
services.AddRebus(
configure => configure
.Transport(t => t.UseAzureServiceBus(connectionString, queueName))
);
and that is all there is to it! ๐ Rebus will use an IHostedService
behind the covers to manage the Rebus instance, so it'll be started and stopped as the host conducts its background services.
If you need access to something that must be resolved from the container (e.g. configurations and stuff), there's an overload that passes the service provider to the configurer:
services.AddRebus(
(configure, provider) => {
var asb = provider.GetRequiredService<IOptions<AsbSettings>>();
var connectionString = asb.ConnectionString;
var queueName = asb.InputQueueName;
return configure
.Transport(t => t.UseAzureServiceBus(connectionString, queueName));
}
);
If you want to subscribe to stuff at startup, use the onCreated
callback:
services.AddRebus(
(configure, provider) => {
var asb = provider.GetRequiredService<IOptions<AsbSettings>>();
var connectionString = asb.ConnectionString;
var queueName = asb.InputQueueName;
return configure
.Transport(t => t.UseAzureServiceBus(connectionString, queueName));
},
onCreated: async bus => {
await bus.Subscribe<FirstEvent>();
await bus.Subscribe<SecondEvent>();
}
);
and that's basically it.
If you're interested in hosting multiple Rebus instances inside a single process, please read on. ๐
โน๏ธ Please note that logging will be automatically configured - if possible, and if you haven't configured anything yourself.
As logging is integrated with the host, Rebus will simply direct all of its logging to loggers created using
the ILoggerFactory
provided by the host, so if you want to log by some other means (e.g. with Serilog), you can
simply use the appropriate Serilog integration package and direct the host's logs to a Serilog sink.
It's still possible to e.g. append the usual .Logging(l => l.Serilog())
if you want to do that, but it's often easier
to just let Rebus use the host logging mechanism, and then configure logging in the host like you would anyway.
It can still be used outside of the generic host, but that will require usage to follow a pattern like this:
var services = new ServiceCollection();
services.AddRebus(...);
using var provider = services.BuildServiceProvider();
// THIS ๐ will start the bus(es)
provider.StartRebus();
โ With the generic host (which is what you're using, if you've created a console app, a background worker, or a web app), the configuration extensions
in this package rely on IHostedService
and how the host uses these, and therefore the above call to StartRebus()
shoule NOT be made.
โน With the generic host, there's two major modes of operation:
- Starting one or more Rebus instances, using the host's container instance
- Starting one or more Rebus instances, using one or more separate container instances
When sharing the host's container instance, starting one or more Rebus instances is as easy as
services.AddRebus(...);
as many times as you like.
โ But please note that there can be only ONE PRIMARY Rebus instance so you'll most likely follow a pattern like this:
// This one ๐ will be the primary bus instance
services.AddRebus(...);
services.AddRebus(isPrimaryBus: false, ...);
services.AddRebus(isPrimaryBus: false, ...);
if you want to add multiple Rebus instances.
When you add a Rebus instance with AddRebus
, you can configure it the way you're used to via its RebusConfigurer
and its extensions, so it could be something like
services.AddRebus(
configure => configure
.Transport(t => t.UseAzureServiceBus(connectionString, "my-queue-name"))
.Serializer(s => s.UseSystemTextJson())
);
for a single bus instance (which is also the default), or something like
services.AddRebus(
configure => configure
.Transport(t => t.UseAzureServiceBus(connectionString, "some-kind-of-processor"))
.Serializer(s => s.UseSystemTextJson())
);
services.AddRebus(
isPrimaryBus: false,
configure: configure => configure
.Transport(t => t.UseAzureServiceBus(connectionString, "some-kind-of-background-processor"))
.Serializer(s => s.UseSystemTextJson())
);
to add two bus instances. If you want to subscribe to something when starting up, there's an optional onCreated
parameter that makes this possible, e.g. like this:
services.AddRebus(
configure => configure
.Transport(t => t.UseAzureServiceBus(connectionString, "my-queue-name"))
.Serializer(s => s.UseSystemTextJson()),
onCreated: async bus => {
await bus.Subscribe<SomethingInterestingHappened>();
await bus.Subscribe<AnotherInterestingThingHappened>();
}
);
The callback passed as onCreated
will be executed when the bus is created and operational, but before it has begun consuming messages.
When you want to host additional Rebus instances in the same process, but you want an extra degree of separation from your host application (e.g. if you're following the
"modular monolith" approach), you can call the AddRebusService
on the host builder for each independent background service you would like to add.
Since the background services are separate from the host, they each have their own container instance, and so you'll need to register whatever stuff they need to work.
In your startup code, you'll most likely find something that goes like this:
var builder = WebApplication.CreateBuilder(args);
// register stuff in builder.Services here
var app = builder.Build();
// maybe configure web app middlewares here
await app.RunAsync();
This builder has a Host
property, which is where you will find the AddRebusService
extension method:
builder.Host.AddRebusService(
services => (...)
);
It has a single callback, which is where you can configure the necessary services for the container instance dedicated to this background service. It requires that at
least ONE call be made to AddRebus
like this:
builder.Host.AddRebusService(
services => services.AddRebus(...)
);
but otherwise it works like your normal service collection. This also means that it's totally fine to add multiple Rebus instance to it as well, if you like (but you'll probably want to mostly stay away from that to avoid giving your team mates a headache ๐คฏ).
A typical configuraion could look like this (assuming that you're into the aesthetics of wrapping your registrations behind IServiceCollection
extensions):
builder.Host.AddRebusService(
services => {
services.AddMyEfDatabaseContext();
services.AddMyRepositories();
services.AddMyApplicationServices();
services.AddRebus(
configure => configure
.Transport(t => t.UseAzureServiceBus(connectionString, "my-queue-name"))
);
services.AddRebusHandler<SomeMessageHandler>();
services.AddRebusHandler<AnotherMessageHandler>();
}
);
โน When using the separate background service approach described here, the container will forward calls for
Microsoft.Extensions.Hosting.IHostApplicationLifetime
Microsoft.Extensions.Logging.ILoggerFactory
to the host's container, which essentially makes these things transparently available, as singletons, to the separate Rebus service.
If you forward requests for other singletons to the host's container you can add to the list of forwarded singleton types:
builder.Host.AddRebusService(
services => services.AddRebus(...),
typeof(IMyDateTimeProvider),
typeof(IMyImportantService),
... etc
);
When adding multiple bus instances to a single container instance, there's one big question that needs to be answered: Which bus instance will be returned if you resolve
IBus
from it?
var bus = serviceProvider.GetRequiredService<IBus>();
// ???
or if you have an IBus
injected:
public class SomethingPublisher
{
public SomethingPublisher(IBus bus) => ... //< ???
}
This where the concept of "default bus" is relevant.
If you need to be able to later resolve a specific bus instance from the service provider, you can register a bus with a key:
services.AddRebus(
configure => configure
.Transport(t => t.UseAzureServiceBus(connectionString, queueName)),
key: "my-favorite-bus"
);
Later, when your app is running, you will then be able to retrieve that specific bus instance via IBusRegistry
like so:
var registry = provider.GetRequiredService<IBusRegistry>(); //< or have this injected
var bus = registry.GetBus("my-favorite-bus");
// voilรก! ๐
When you configure the bus, e.g. with
services.AddRebus(
configure => configure
.Transport(t => t.UseAzureServiceBus(connectionString, queueName)),
key: "my-favorite-bus"
);
and then the host starts up (or you call the StartRebus()
extension method on the service provider), the bus will automatically be started (i.e.
it will start consuming messages).
You can delay the time of when message consumption is begun by setting startAutomatically: false
in the call to AddRebus
:
services.AddRebus(
configure => configure
.Transport(t => t.UseAzureServiceBus(connectionString, queueName)),
key: "my-favorite-bus",
startAutomatically: false //< the bus will be "started" with 0 workers, i.e. it will not consume anything
);
At a later time, when you think it's about time the bus gets to taste some sweet message goodness, you can start it via the registry:
var registry = provider.GetRequiredService<IBusRegistry>(); //< or have this injected
registry.StartBus("my-favorite-bus");
// voilรก! ๐
Since starting the bus this way requires that you retrieve it via the bus registry, it's a hard requirement that a KEY is provided when calling AddRebus
.