Bus Configuration
MassTransit is usable in most .NET application types. MassTransit is easily configured in ASP.NET Core or .NET Generic Host applications (using .NET 6 or later).
To use MassTransit, add the MassTransit package (from NuGet) and start with the AddMassTransit method shown below.
using MassTransit;
services.AddMassTransit(x =>{ // A Transport x.UsingRabbitMq((context, cfg) => { });});In this configuration, the following variables are used:
| Variable | Description |
|---|---|
| x | Configure the bus instance (not transport specific) and the underlying service collection. The callback passed to the UsingRabbitMq method is invoked after the container service provider has been built. Any methods to configure the bus instance (using x) should be called outside of this callback. |
| context | The configured bus context, also implements IServiceProvider |
| cfg | Configure the bus specific to the transport (each transport has its own interface type |
Container Registrations
Section titled âContainer RegistrationsâAdding MassTransit, as shown above, will configure the service collection with required components, including:
Scoped Interfaces
Section titled âScoped Interfacesâ| Interface | Notes |
|---|---|
| IScopedBus (New in v9) |
Combines ISendEndpointProvider, IPublishEndpoint, and IScopedClientFactory into a single interface that can be used in any scoped context (with or without a consumer) |
| ISendEndpointProvider | Send messages from consumer dependencies, ASP.NET Controllers |
| IPublishEndpoint | Publish messages from consumer dependencies, ASP.NET Controllers |
| IScopedClientFactory | Used to create request clients (singleton, or within scoped consumers) |
| IRequestClient |
Used to send requests |
| ConsumeContext | Available in any message scope, such as a consumer, saga, or activity, is also an ISendEndpointProvider and IPublishEndpoint |
Singleton Interfaces
Section titled âSingleton Interfacesâ| Interface | Notes |
|---|---|
| IReceiveEndpointConnector | Used to connect additional Receive endpoints to the bus after the bus is started |
| IBus | The bus instance, not typically used in modern applications |
| IBusControl | Used to start/stop the bus (not typically used) |
| IClientFactory | Used to create request clients (singleton, or within scoped consumers) |
| MassTransit Hosted Service | Automatically starts and stops the bus with the .NET host |
| MassTransit Health Checks | Health checks for each bus instance (including MultiBus) |
MassTransit uses Microsoft.Extensions.Logging for all log output. Therefore, MassTransit
will use any existing logging configuration. See Logging for more information.
Host Options
Section titled âHost OptionsâMassTransit adds a hosted service so that the generic host can start and stop the bus (or buses if multiple bus instances are configured). The host options can be configured via MassTransitHostOptions using the Options pattern as shown below.
services.AddOptions<MassTransitHostOptions>() .Configure(options => { });| Option | Description |
|---|---|
| WaitUntilStarted | By default, MassTransit connects to the broker asynchronously. When set to true, the MassTransit Hosted Service will block startup until the broker connection has been established. |
| StartTimeout | By default, MassTransit waits infinitely until the broker connection is established. If specified, MassTransit will give up after the timeout has expired. |
| StopTimeout | MassTransit waits infinitely for the bus to stop, including any active message consumers. If specified, MassTransit will force the bus to stop after the timeout has expired. |
| ConsumerStopTimeout | If specified, the ConsumeContext.CancellationToken will be canceled after the specified timeout when the bus is stopping. This allows long-running consumers to observe the cancellation token and react accordingly. Must be <= the StopTimeout |
Transport Options
Section titled âTransport OptionsâEach supported transport can be configured via a .Host() method or via the
.NET Options Pattern.
- Rabbit MQ: RabbitMqTransportOptions
- Azure Service Bus: AzureServiceBusTransportOptions
- Amazon SQS: AmazonSqsTransportOptions
Consumer Registration
Section titled âConsumer RegistrationâTo consume messages, one or more consumers must be added and Receive endpoints configured for the added consumers. MassTransit connects each Receive endpoint to a queue on the message broker.
To add a consumer and automatically configure a Receive endpoint for the consumer, call one of the AddConsumer methods and call ConfigureEndpoints as shown below.
services.AddMassTransit(x =>{ x.AddConsumer<SubmitOrderConsumer>();
x.UsingRabbitMq((context, cfg) => { cfg.ConfigureEndpoints(context); });});Any consumer dependencies should be added to the container separately. Consumers are registered as scoped, and dependencies should be registered as scoped when possible, unless they are singletons.
MassTransit will automatically configure a Receive endpoint for the SubmitOrderConsumer using the name returned by the configured endpoint name formatter.
When the bus is started, the Receive endpoint will be started and messages will be delivered from the queue by the transport to an instance of the consumer.
All consumer types can be added, including consumers, sagas, saga state machines, and routing slip activities. If a job consumer is added, additional configuration is required.
To exclude a consumer, saga, or routing slip activity from automatic configuration, use the ExcludeFromConfigureEndpoints extension method when adding the consumer:
x.AddConsumer<SubmitOrderConsumer>() .ExcludeFromConfigureEndpoints()Alternatively, the ExcludeFromConfigureEndpoints attribute may be specified on the consumer.
[ExcludeFromConfigureEndpoints]public class SubmitOrderConsumer : IConsumer<SubmitOrder>{}Configure Endpoints
Section titled âConfigure EndpointsâAs shown in the example above, using ConfigureEndpoints is the preferred approach to configure Receive endpoints. By registering consumers, sagas, and routing
slip activities along with their optional definitions, MassTransit is able to configure Receive endpoints for all registered consumer types. Receive endpoint
names are generated using an endpoint name formatter (unless otherwise specified in a definition), and each Receive endpoint is
configured.
As Receive endpoints are configured, one or more consumer types are configured on each Receive endpoint. If multiple consumer types share the same endpoint name, those consumer types will be configured on the same Receive endpoint. For each consumer type, its respective consumer, saga, or activity definition will be applied to the Receive endpoint.
Configure Endpoints Callback
Section titled âConfigure Endpoints CallbackâTo apply Receive endpoint settings or configure middleware for all Receive endpoints configured by ConfigureEndpoints, a callback can be added.
x.AddConfigureEndpointsCallback((name, cfg) =>{ cfg.UseMessageRetry(r => r.Immediate(2));});When ConfigureEndpoints is called, any registered callbacks will be called for every Receive endpoint. Each callback will only be called once per
Receive endpoint.
To conditionally apply transport-specific settings, the cfg parameter can be pattern-matched to the transport type as shown below.
x.AddConfigureEndpointsCallback((name, cfg) =>{ if (cfg is IRabbitMqReceiveEndpointConfigurator rmq) rmq.SetQuorumQueue(3);
cfg.UseMessageRetry(r => r.Immediate(2));});Endpoint Mapping Strategies
Section titled âEndpoint Mapping StrategiesâMassTransit supports several ways to map consumers to queues. One consumer type per Receive endpoint (queue) is a good starting point for most applications, but there are situations where sharing a queue makes sense.
One Consumer per Queue
Section titled âOne Consumer per QueueâBy default, ConfigureEndpoints creates an endpoint for each registered consumer or saga, named using the endpoint name formatter.
For most applications, this approach is just right. Each consumer can be configured independently, including prefetch, concurrency, retry, redelivery, and the outbox. A slow or busy consumer also wonât leave unrelated messages waiting behind its messages in the same queue. This is particularly useful when consumers have different throughput or response-time requirements.
Multiple Consumers per Queue
Section titled âMultiple Consumers per QueueâSharing a queue can make sense when consumers perform closely related work and have similar throughput, retry, and resource requirements. They share the queueâs settings and available concurrency, so consider whether a busy consumer or one waiting to retry could hold up the others. Being in the same domain or namespace isnât usually enough reason on its own to group consumers together.
To group consumers, give them the same endpoint name when using ConfigureEndpoints, or
configure them explicitly on one Receive endpoint. Be sure to configure shared middleware only once, since consumer definitions can otherwise
add duplicate filters.
Sharing a queue does not, by itself, guarantee that messages will be processed in order.
Consume Multiple Message Types
Section titled âConsume Multiple Message TypesâIf the message types are closely related and use the same dependencies, it may be simpler to have one consumer handle them. Add an IConsumer<T> interface
for each message type, as shown below.
public class AddressConsumer : IConsumer<CreateAddress>, IConsumer<UpdateAddress>{}Conventional consumers, available in v9.2.1 and later, offer another way to handle related message types in
one class. Each Consume method can request just the dependencies it needs, which is useful when creating and updating an address require different services.
[Consumer]public class AddressConsumer : IConsumer{ public Task Consume(CreateAddress message, IAddressCreator creator, CancellationToken cancellationToken) { return creator.Create(message, cancellationToken); }
public Task Consume(UpdateAddress message, IAddressUpdater updater, CancellationToken cancellationToken) { return updater.Update(message, cancellationToken); }}Register this consumer with AddConsumer<AddressConsumer>(), just like an interface-based consumer. The dependencies are resolved from the container for each
method, so thereâs no need for a constructor that takes every dependency used by every message type.
All message types handled by that consumer use the same queue. Saga state machines similarly receive their events on a single queue by default.
All Consumers on a Single Queue
Section titled âAll Consumers on a Single QueueâWhile supported, this approach is generally discouraged. Everything shares the same queue and available concurrency, so one busy or slow consumer can affect the entire service. As the service grows, it becomes harder to tune individual consumers or work out whatâs holding up messages.
Does Having More Queues Add Overhead?
Section titled âDoes Having More Queues Add Overhead?âYes, each endpoint adds broker resources and topology to configure at startup. That is a tradeoff worth considering, but reducing the queue count also means giving up some isolation and control. Start with the layout that suits your consumers, then measure startup time and broker resource usage as the application grows. For RabbitMQ, topology configuration concurrency can help reduce startup pressure when configuring many endpoints.
How Much Concurrency Should I Allow?
Section titled âHow Much Concurrency Should I Allow?âA useful starting point is how much work your dependencies can comfortably handle at once. Separate queues still share resources such as database connection pools and HTTP services, so consider the combined activity across endpoints rather than tuning each one in isolation. Leave room for other application work, and account for additional service instances using the same dependencies.
Use ConcurrentMessageLimit to control how many messages are processed at once. Setting it at the bus level
provides a default for each endpoint, not a shared limit across the bus. For example, 20 busy endpoints with a limit of 5 can process up to 100 messages
concurrently in one service instance. You can override that default per endpoint, or use a
consumer definition to further limit an individual consumer.
Tune PrefetchCount alongside concurrency, since it controls how many unacknowledged messages an endpoint receives. Watch throughput and dependency usage under
load, then adjust the limits to suit the application.
When Should Consumers Move into Separate Services?
Section titled âWhen Should Consumers Move into Separate Services?âConsumers that share ownership, a data model, and a deployment lifecycle often fit well in the same service, even when they use separate queues. Consumer count alone isnât a particularly useful reason to split a service.
Consider a separate service when a group of consumers needs to be deployed or scaled independently. For example, a slow or high-volume consumer may benefit from its own resources and concurrency settings. Keep shared dependencies in mind, though: moving consumers into another service wonât give a shared database more capacity.
Endpoint Name Formatters
Section titled âEndpoint Name FormattersâConfigureEndpoints uses an IEndpointNameFormatter to format the queue names for all supported consumer types. The default endpoint name formatter returns
PascalCase class names without the namespace. There are several built-in endpoint name formatters included. For the SubmitOrderConsumer, the receive
endpoint names would be formatted as shown below. Note that class suffixes such as Consumer, Saga, and Activity are trimmed from the endpoint name by
default.
| Format | Configuration | Name |
|---|---|---|
| Default | SetDefaultEndpointNameFormatter |
SubmitOrder |
| Snake Case | SetSnakeCaseEndpointNameFormatter |
submit_order |
| Kebab Case | SetKebabCaseEndpointNameFormatter |
submit-order |
The endpoint name formatters can also be customized by constructing a new instance and configuring MassTransit to use it.
x.SetEndpointNameFormatter(new KebabCaseEndpointNameFormatter(prefix: "Dev", includeNamespace: false));By specifying a prefix, the endpoint name would be dev-submit-order. This is useful when sharing a single broker with multiple developers (Amazon SQS is
account-wide, for instance).
Receive Endpoints
Section titled âReceive EndpointsâThe previous examples use conventions to configure Receive endpoints. Alternatively, Receive endpoints can be explicitly configured.
When configuring endpoints manually, ConfigureEndpoints should be excluded or be called after any explicitly configured Receive endpoints.
To explicitly configure endpoints, use the ConfigureConsumer or ConfigureConsumers method.
services.AddMassTransit(x =>{ x.AddConsumer<SubmitOrderConsumer>();
x.UsingRabbitMq((context, cfg) => { cfg.ReceiveEndpoint("order-service", e => { e.ConfigureConsumer<SubmitOrderConsumer>(context); }); });});Receive endpoints have transport-independent settings that can be configured.
| Name | Description | Default |
|---|---|---|
| PrefetchCount | Number of unacknowledged messages delivered by the broker | max(CPU Count x 2,16) |
| ConcurrentMessageLimit | Number of concurrent messages delivered to consumers | (none, uses PrefetchCount) |
| ConfigureConsumeTopology | Create exchanges/topics on the broker and bind them to the Receive endpoint | true |
| ConfigureMessageTopology | Create exchanges/topics on the broker and bind them to the Receive endpoint for a specific message type | true |
| PublishFaults | Publish Fault<T> events when consumers fault |
true |
| DefaultContentType | The default content type for received messages | See serialization |
| SerializerContentType | The default content type for sending/publishing messages | See serialization |
The PrefetchCount, ConcurrentMessageLimit, and serialization settings can be specified at the bus level and will be applied to all Receive endpoints.
In the following example, the PrefetchCount is set to 32 and the ConcurrentMessageLimit is set to 28.
services.AddMassTransit(x =>{ x.AddConsumer<SubmitOrderConsumer>();
x.UsingRabbitMq((context, cfg) => { cfg.PrefetchCount = 32; // applies to all receive endpoints
cfg.ReceiveEndpoint("order-service", e => { e.ConcurrentMessageLimit = 28; // only applies to this endpoint e.ConfigureConsumer<SubmitOrderConsumer>(context); }); });});When using ConfigureConsumer with a consumer that has a definition, the EndpointName, PrefetchCount, and Temporary properties of the consumer definition are not used.
Temporary Endpoints
Section titled âTemporary EndpointsâSome consumers only need to receive messages while connected, and any messages published while disconnected should be discarded. This can be achieved by using a TemporaryEndpointDefinition to configure the Receive endpoint.
services.AddMassTransit(x =>{ x.AddConsumer<SubmitOrderConsumer>();
x.UsingInMemory((context, cfg) => { cfg.ReceiveEndpoint(new TemporaryEndpointDefinition(), e => { e.ConfigureConsumer<SubmitOrderConsumer>(context); });
cfg.ConfigureEndpoints(context); });});Dynamic Endpoints
Section titled âDynamic EndpointsâTo dynamically configure a Receive endpoint on an existing bus, resolve the IReceiveEndpointConnector interface (either via dependency injection or from an
IServiceProvider interface) and call ConnectReceiveEndpoint as shown.
var connector = provider.GetRequiredService<IReceiveEndpointConnector>();
var handle = connector.ConnectReceiveEndpoint("queue-name", (context, cfg) =>{ cfg.ConfigureConsumer<MyConsumer>(context);});
// optional, the handle can be used to wait for the receive endpoint to finish connectingawait handle.Ready;There is also an override for connecting the Receive endpoint using an endpoint definition. For instance, to create a temporary Receive endpoint (typically used for fan-out event consumers), pass the endpoint definition as shown.
var connector = provider.GetRequiredService<IReceiveEndpointConnector>();var endpointNameFormatter = provider.GetService<IEndpointNameFormatter>() ?? DefaultEndpointNameFormatter.Instance;
var handle = connector.ConnectReceiveEndpoint(new TemporaryEndpointDefinition(), endpointNameFormatter, (context, cfg) =>{ cfg.ConfigureConsumer<UpdateCacheEventConsumer>(context);});See the endpoint name formatters section above for details.
Endpoint Configuration
Section titled âEndpoint ConfigurationâTo configure the endpoint for a consumer registration, or override the endpoint configuration in the definition, the Endpoint method can be added to the
consumer registration. This will create an endpoint definition for the consumer and register it in the container. This method is available on consumer and saga
registrations, with separate execute and compensate endpoint methods for activities.
services.AddMassTransit(x =>{ x.AddConsumer<SubmitOrderConsumer, SubmitOrderConsumerDefinition>() .Endpoint(e => { // override the default endpoint name e.Name = "order-service-extreme";
// more options shown below });
x.UsingRabbitMq((context, cfg) => cfg.ConfigureEndpoints(context));});The configurable settings for an endpoint include:
| Property | Description |
|---|---|
| Name | The Receive endpoint (queue) name |
| InstanceId | If specified, should be unique for each bus instance To enable fan-out (instead of load balancing) |
| Temporary | If true, the endpoint will be automatically removed after the bus has stopped. |
| PrefetchCount | Number of unacknowledged messages delivered by the broker |
| ConcurrentMessageLimit | Number of concurrent messages delivered to consumers |
| ConfigureConsumeTopology | Create exchanges/topics on the broker and bind them to the Receive endpoint |
| AddConfigureEndpointCallback | (Added in v8.3.0) Adds a callback method that will be invoked when the Receive endpoint is configured. |
Endpoint Naming
Section titled âEndpoint NamingâWhen the endpoint is configured after the AddConsumer method, the configuration then overrides the endpoint configuration in the consumer definition. However,
it cannot override the EndpointName if it is specified in the constructor. The order of precedence for endpoint naming is explained below.
-
Specifying
EndpointName = "submit-order-extreme"in the constructor which cannot be overriddenx.AddConsumer<SubmitOrderConsumer, SubmitOrderConsumerDefinition>()public SubmitOrderConsumerDefinition(){EndpointName = "submit-order-extreme";} -
Specifying
.Endpoint(x => x.Name = "submit-order-extreme")in the consumer registration, chained toAddConsumerx.AddConsumer<SubmitOrderConsumer, SubmitOrderConsumerDefinition>().Endpoint(x => x.Name = "submit-order-extreme");public SubmitOrderConsumerDefinition(){Endpoint(x => x.Name = "not used");} -
Specifying
Endpoint(x => x.Name = "submit-order-extreme")in the constructor, which creates an endpoint definitionx.AddConsumer<SubmitOrderConsumer, SubmitOrderConsumerDefinition>()public SubmitOrderConsumerDefinition(){Endpoint(x => x.Name = "submit-order-extreme");} -
Unspecified, the endpoint name formatter is used (in this case, the endpoint name is
SubmitOrderusing the default formatter)x.AddConsumer<SubmitOrderConsumer, SubmitOrderConsumerDefinition>()public SubmitOrderConsumerDefinition(){}
Health Checks
Section titled âHealth ChecksâThe AddMassTransit method adds an IHealthCheck to the service collection that you can use to monitor your health. The health check is added with the tags
ready and masstransit.
To configure health checks, map the ready and live endpoints in your ASP.NET application.
app.MapHealthChecks("/health/ready", new HealthCheckOptions(){ Predicate = (check) => check.Tags.Contains("ready"),});
app.MapHealthChecks("/health/live", new HealthCheckOptions());Example Output
{ "status": "Healthy", "totalDuration": "00:00:00.2134026", "entries": { "masstransit-bus": { "data": { "Endpoints": { "rabbitmq://localhost/dev-local/SubmitOrder": { "status": "Healthy", "description": "ready" } } }, "description": "Ready", "duration": "00:00:00.1853530", "status": "Healthy", "tags": [ "ready", "masstransit" ] } }}- When everything works correctly, MassTransit will report
Healthy. - If any problems occur on application startup, MassTransit will report
Unhealthy. This can cause an orchestrator to restart your application. - If any problems occur while the application is working (for example, application loses connection to broker), MassTransit will report
Degraded.
Health Check Options
Section titled âHealth Check OptionsâHealth Checks can be further configured using ConfigureHealthCheckOptions:
builder.Services.AddMassTransit(bus =>{ bus.ConfigureHealthCheckOptions(options => { options.Name = "masstransit"; options.MinimalFailureStatus = HealthStatus.Unhealthy; options.Tags.Add("health"); });
}| Setting | Description | Default value |
|---|---|---|
| Name | Set the health check name, overrides the default bus type name. | Bus name. |
| MinimalFailureStatus | The minimal HealthStatus that will be reported when the health check fails. |
Unhealthy |
| Tags | A list of tags that can be used to filter sets of health checks. | âreadyâ, âmasstransitâ |
By default, MassTransit reports all three statuses depending on the application state.
If MinimalFailureStatus is set to Healthy, MassTransit will log any issues, but the health check will always report Healthy.
If MinimalFailureStatus is set to Degraded, MassTransit will report Degraded if any issues occur, but never report Unhealthy.
Tags inside options will override default tags. You will need to add ready and masstransit tags manually if you want to keep them.