NServiceBus endpoints are hosted with Microsoft. by registering them on IServiceCollection using the AddNServiceBusEndpoint method. Endpoint startup, dependency injection, and logging align with the standard .NET hosting model, and the same registration approach supports both single-endpoint and multi-endpoint hosts.
Hosting a single endpoint
Register the endpoint on the host's service collection:
var builder = Host.CreateApplicationBuilder();
var endpointConfiguration = new EndpointConfiguration("Sales");
// configure transport, persistence, etc.
builder.Services.AddNServiceBusEndpoint(endpointConfiguration);
await builder.Build().RunAsync();
The endpoint starts and stops with the host's lifecycle. IMessageSession is registered on the host's IServiceCollection and can be resolved from the service provider:
var builder = Host.CreateApplicationBuilder();
var endpointConfiguration = new EndpointConfiguration("Sales");
// configure transport, persistence, etc.
builder.Services.AddNServiceBusEndpoint(endpointConfiguration);
var host = builder.Build();
var messageSession = host.Services.GetRequiredService<IMessageSession>();
Or injected into controllers, background services, or any other component that needs to send or publish from outside a message handler or other NServiceBus extension point:
class OrderService(IMessageSession session)
{
public Task Submit(Order order) => session.Send(order);
}
For most applications, a single endpoint per process should be the default.
Hosting multiple endpoints
NServiceBus also supports hosting multiple logical endpoints in one process. Common scenarios:
- Multi-tenant systems where each tenant requires an isolated endpoint
- Modular monoliths where each module owns its own endpoint within a shared host
- Partitioned throughput where each partition is an endpoint sharing a host
- Competing consumers co-located in the same process that require a shared in-memory synchronization primitive
- Co-located infrastructure endpoints that do not justify a separate process (for example to save the memory overhead of multiple processes to achieve a more dense hosting)
Compared to a single-endpoint host, each additional endpoint adds registration, startup, and coordination overhead within the shared process.
Each endpoint is registered with its own EndpointConfiguration. The second argument to AddNServiceBusEndpoint is the endpoint identifier — a service key used to distinguish that endpoint's IMessageSession and per-endpoint keyed services within the dependency injection container:
var salesConfig = new EndpointConfiguration("Sales");
var billingConfig = new EndpointConfiguration("Billing");
builder.Services.AddNServiceBusEndpoint(salesConfig, salesConfig.EndpointName);
builder.Services.AddNServiceBusEndpoint(billingConfig, billingConfig.EndpointName);
It is recommended to encapsulate endpoint specific configuration including the call to builder.inside an extension method to keep the composition root lean and simple to understand.
builder.Services.AddSalesEndpoint();
builder.Services.AddBillingEndpoint();
Most of the time, the endpoint name is the ideal identifier to separate endpoint-specific services in the service collection. A distinct endpoint identifier is only needed when the same endpoint is registered more than once in a process — for example, a per-tenant deployment where each tenant has the same point but with its own input queue:
foreach (var tenant in tenants)
{
var key = $"Sales-{tenant}";
var endpointConfig = new EndpointConfiguration("Sales");
endpointConfig.OverrideLocalAddress(key);
// additional per-tenant configuration
builder.Services.AddNServiceBusEndpoint(endpointConfig, key);
}
All tenants share the Sales endpoint name; each gets its own input queue. A tenant-scoped identifier (Sales-) lets callers resolve a specific tenant's IMessageSession and keyed services. Including the endpoint name in the identifier keeps it unique when other endpoints (for example, Billing) are also hosted per tenant in the same process.
Endpoint-scoped dependencies
When each endpoint requires a different implementation of a shared service, register it as a keyed service using the chosen endpoint identifier (typically the endpoint name) as the key:
var salesConfig = new EndpointConfiguration("Sales");
var billingConfig = new EndpointConfiguration("Billing");
var salesDb = new DatabaseService("sales-db");
var billingDb = new DatabaseService("billing-db");
builder.Services.AddKeyedSingleton<DatabaseService>(salesConfig.EndpointName, salesDb);
builder.Services.AddKeyedSingleton<DatabaseService>(billingConfig.EndpointName, billingDb);
builder.Services.AddNServiceBusEndpoint(salesConfig, salesConfig.EndpointName);
builder.Services.AddNServiceBusEndpoint(billingConfig, billingConfig.EndpointName);
In the snippet above each endpoint resolves its own DatabaseService instance as keyed services using the previously chosen identifier as a key. Services that do not vary per endpoint are registered normally (non-keyed) on IServiceCollection and every endpoint resolves the same instance.
Resolving services per endpoint
Inside NServiceBus extension points — message handlers, sagas, features, and installers — per-endpoint services resolve automatically from the right scope. [FromKeyedServices] is not needed in those contexts.
Outside extension points (for example, controllers, background services, or any host-level component that needs to send or publish), use GetRequiredKeyedService from the service provider or [FromKeyedServices(identifier)] for constructor injection:
var salesSession = serviceProvider.GetRequiredKeyedService<IMessageSession>("Sales");
var billingSession = serviceProvider.GetRequiredKeyedService<IMessageSession>("Billing");
class SalesOrderService([FromKeyedServices("Sales")] IMessageSession session)
{
public Task Submit(Order order) => session.Send(order);
}
Non-keyed (global) services and per-endpoint keyed services can be mixed in the same constructor:
// MyGlobalService is a non-keyed (global) service; IMessageSession is keyed for the "Sales" endpoint.
class SalesOrderRouter(MyGlobalService service, [FromKeyedServices("Sales")] IMessageSession session)
{
public Task Submit(Order order)
{
service.DoSomething();
return session.Send(order);
}
}
Understanding endpoint identity
Two identifiers describe an endpoint:
| Endpoint name | Endpoint identifier | |
|---|---|---|
| Identifies | The logical endpoint (queue, routing, log context) | The DI registration for IMessageSession and per-endpoint keyed services |
| Set via | new EndpointConfiguration(name) | Second argument to AddNServiceBusEndpoint(config, endpointIdentifier) |
| Required | Always | When more than one endpoint is registered on the same IServiceCollection |
| Unique across | Logical endpoints in the system | Endpoint registrations in the process |
When an endpoint identifier is needed, the endpoint name is the recommended value. A different value is only needed when the same endpoint definition is hosted more than once in one process — for example, a per-tenant deployment. See Hosting multiple endpoints for the registration patterns.
Logging
NServiceBus uses Microsoft. as its built-in logging infrastructure. Log events flow through the host's configured ILoggerFactory with endpoint context attached.