Starting with Version 6, the IBus
interface has been deprecated.
The IBus
interface was one of the key interfaces when using previous versions. It provided access to many of the operations like sending messages, subscribing to events, and manipulating headers. The IBus
interface was available through dependency injection in message handlers, any custom components that were registered in the container and through other mechanisms like the saga base class.
Reasons for deprecating IBus
The IBus
interface contained numerous methods and properties. However, not all of them are valid in all scenarios where an instance of IBus
was exposed and this can lead to confusion. For example, the methods Reply
and ForwardCurrentMessageTo
are always available on the IBus
interface, but they are only relevant in the context of handling an incoming message. Using them in other scenarios will throw an exception.
For users new to the API, the fact that IBus
was available in message handlers using Dependency Injection wasn't obvious, especially when trying to send or publish messages from inside message handlers. Rather than using dependency injection, an IMessageHandlerContext
parameter is now available in all the message handlers. This parameter exposes all of the appropriate actions to the message handler. Methods that aren't applicable will not be available making the API simpler.
All of the previous Bus
methods now available via the IMessageHandlerContext
parameter in the message handlers and the methods in the IMessageSession
interface available when the endpoint is started are fully async. However, the original method names are retained rather than adding the async suffix to make the upgrade easier.
Migrating away from IBus
Some scenarios involving IBus
include: endpoint creation, sending messages during endpoint startup, sending messages from within message handlers, using the injected IBus
in custom components also registered in the container.
During Endpoint Creation
In previous versions to start a new instance of an endpoint, either the Bus
static class or the IStartableBus
interface was used. In Version 6 these two concepts have been replaced. More information can be found in the hosting section of the documentation. Instead of the Bus
class, a new class called Endpoint
helps to start the endpoint.
BusConfiguration
has been deprecated. Use EndpointConfiguration
instead to configure and start the endpoint. Because IBus
has been deprecated, the BusConfiguration
class has been renamed to EndpointConfiguration
to keep terminology consistent.
6.x NServiceBus
var endpointConfiguration = new EndpointConfiguration("EndpointName");
// Custom code before start
var endpointInstance = await Endpoint.Start(endpointConfiguration)
.ConfigureAwait(false);
// Custom code after start
// Block the process
// Custom code before stop
await endpointInstance.Stop()
.ConfigureAwait(false);
// Custom code after stop
5.x NServiceBus
var busConfiguration = new BusConfiguration();
// Custom code before start
var startableBus = Bus.Create(busConfiguration);
using (var bus = startableBus.Start())
{
// Custom code after start
// Block the process
// Custom code before stop
}
// Custom code after stop
Starting the endpoint provides access to IEndpointInstance
which can be used to send messages during endpoint start up instead of using the IBus
interface.
"Send-only" endpoints were created by calling the CreateSendOnly
method on the Bus
class in previous versions. In version 6 there is no longer a separate method to create or start "Send-Only" endpoints. Configure the endpoint to be "Send-Only" with the SendOnly
method on EndpointConfiguration
and create/start it with the Endpoint
class.
Accessing the CurrentMessageContext
Previously it was possible to access message parameters, such as MessageId
, ReplyToAddress
and the message headers, via the CurrentMessageContext
property on the IBus
interface. These message properties are now available directly via the message handler context parameter.
6.x NServiceBus
public Task Handle(MyMessage message, IMessageHandlerContext context)
{
var messageId = context.MessageId;
var replyToAddress = context.ReplyToAddress;
var headers = context.MessageHeaders;
return Task.CompletedTask;
}
Sending messages inside message handlers
Instances of IBus
that were being injected into message handler classes by dependency injection can be safely deleted.
The message handler signature now includes an additional IMessageHandlerContext
parameter, which provides the methods that used to be called from IBus
. Use the IMessageHandlerContext
to send and publish messages from within the message handler.
6.x NServiceBus
public class SendAndPublishHandler :
IHandleMessages<MyMessage>
{
public async Task Handle(MyMessage message, IMessageHandlerContext context)
{
await context.Send(new MyOtherMessage())
.ConfigureAwait(false);
await context.Publish(new MyEvent())
.ConfigureAwait(false);
}
}
Sending messages outside message handlers
A common use of IBus
is to invoke bus operations outside of the pipeline (e.g. in handlers, sagas and pipeline extensions), such as sending a message from an ASP.NET request or a client application. Instead of an IBus,
the IMessageSession
offers all available messaging operations outside the message processing pipeline. For example:
6.x NServiceBus
var endpointInstance = await Endpoint.Start(endpointConfiguration)
.ConfigureAwait(false);
IMessageSession messageSession = endpointInstance;
await messageSession.Send(new SomeMessage())
.ConfigureAwait(false);
In this example, a message is being sent during startup of an Endpoint. Hence the IMessageSession
is available via the IEndpointInstance
class from Endpoint.
. Note that implicitly converting from IEndpointInstance
to IMessageSession
is optional but it is preferred as IMessageSession
offers a more concise API for messaging interactions.
For other scenarios (i.e. not at startup) that need access to IMessageSession
there are two ways to achieve this:
- Static property. In this scenario, during startup, the
IMessageSession
instance is assigned to a static property that is accessible from other components. - Injecting via container. See "Dependency Injection" below.
If the endpoint is hosted using NServiceBus.Host, use the IWantToRunWhenEndpointStartsAndStops
interface.
Dependency Injection
In previous versions, the IBus
interface was automatically registered in the IOC container. In Version 6, the new context-aware interfaces, for example, IEndpointInstance
, IMessageSession
and IMessageHandlerContext
, etc., are not automatically registered in dependency injection.
In Versions 5 and below, when a custom component was registered in the container, the custom component had access to the IBus
instance via dependency injection.
IBus
with IMessageSession
. It depends on the usage of the IBus
inside the custom component.Scenario 1: If the custom component was sending messages using the injected IBus
from outside of message handlers, for example, in an MVC Controller class, then it is safe to register the IMessageSession
when self-hosting. This interface can then be used to send messages. See Sending from an ASP.NET MVC Controller for an example of this.
Scenario 2: If the custom component is accessed from within message handlers then the IMessageHandlerContext
parameter should be passed to the custom component instead of the IMessageSession
interface to either send or publish messages.
Some of the dangers when using an IMessageSession
interface inside a message handler to send or publish messages are:
- Those messages will not participate in the same transaction scope as that of the message handler. This could result in messages dispatched or events published via the
IMessageSession
orIEndpointInstance
interface even if the message handler resulted in an exception and the operation was rolled back. - Those messages will not be part of the batching operation.
- Those messages will not contain any important message header information that is available via the
IMessageHandlerContext
interface parameter, e.g., CorrelationId.
Accessing message handler context in the dependency hierarchy
The snippet below shows a handler with a dependency that accesses the IBus
interface. The dependency is injected into a handler and used from within the handler.
6.x NServiceBus
public class MyDependency
{
IBus bus;
public MyDependency(IBus bus)
{
this.bus = bus;
}
public void Do()
{
foreach (var changedCustomer in LoadChangedCustomers())
{
bus.Publish(new CustomerChanged { Name = changedCustomer.Name });
}
}
static IEnumerable<Customer> LoadChangedCustomers()
{
return Enumerable.Empty<Customer>();
}
}
public class HandlerWithDependencyWhichUsesIBus :
IHandleMessagesFromPreviousVersions<MyMessage>
{
MyDependency dependency;
public HandlerWithDependencyWhichUsesIBus(MyDependency dependency)
{
this.dependency = dependency;
}
public void Handle(MyMessage message)
{
dependency.Do();
}
}
Since message handler context operations are asynchronous, it is advised to refactor the dependency to no longer use the bus operations towards a design in which the dependency returns information to the caller that can be used to determine what bus operations are required. The following snippet illustrates that:
6.x NServiceBus
public class MyReturningDependency
{
IBus bus;
public MyReturningDependency(IBus bus)
{
this.bus = bus;
}
public IEnumerable<string> Do()
{
return LoadChangedCustomers().Select(changedCustomer => changedCustomer.Name);
}
static IEnumerable<Customer> LoadChangedCustomers()
{
return Enumerable.Empty<Customer>();
}
}
public class HandlerWithDependencyWhichReturns :
IHandleMessages<MyMessage>
{
MyReturningDependency dependency;
public HandlerWithDependencyWhichReturns(MyReturningDependency dependency)
{
this.dependency = dependency;
}
public async Task Handle(MyMessage message, IMessageHandlerContext context)
{
foreach (var customerName in dependency.Do())
{
await context.Publish(new CustomerChanged { Name = customerName })
.ConfigureAwait(false);
}
}
}
By using this approach, the asynchronous APIs won't ripple through all the layers, and the dependency can remain synchronous if desired. If such a change is not feasible or desired the context has to be floated into the dependency by using method injection like shown below:
6.x NServiceBus
public class MyContextAccessingDependency
{
public async Task Do(IMessageHandlerContext context)
{
foreach (var changedCustomer in LoadChangedCustomers())
{
await context.Publish(new CustomerChanged { Name = changedCustomer.Name })
.ConfigureAwait(false);
}
}
static IEnumerable<Customer> LoadChangedCustomers()
{
return Enumerable.Empty<Customer>();
}
}
public class HandlerWithDependencyWhichAccessesContext :
IHandleMessages<MyMessage>
{
MyContextAccessingDependency dependency;
public HandlerWithDependencyWhichAccessesContext(MyContextAccessingDependency dependency)
{
this.dependency = dependency;
}
public async Task Handle(MyMessage message, IMessageHandlerContext context)
{
// float the context into the dependency via method injection
await dependency.Do(context)
.ConfigureAwait(false);
}
}
Uniform session
If a step by step migration like shown above is not possible or takes a longer period, the uniform session package can be used to unify the message session with the pipeline context into a uniform session. For more information about the uniform session consult the uniform session documentation. It is advised to use this package for the transition phase until the new design approach started with NServiceBus v6 can be fully embraced.
UnicastBus made internal
Accessing the builder
When using the IBuilder
interface outside the infrastructure of NServiceBus, it was possible to use a hack by casting the IBus
interface to UnicastBus
and then accessing the Builder
property like this:
5.x NServiceBus
var builder = ((UnicastBus) bus).Builder;
This is no longer supported. Instead of using IBuilder
directly, it is advised to use dependency injection.
Setting the host information
Control over HostInformation
was previously done using UnicastBus.
. This is now done using a more explicit API to set the host identifier using the endpoint configuration.
6.x NServiceBus
endpointConfiguration.UniquelyIdentifyRunningInstance()
.UsingNames("endpointName", Environment.MachineName);
// or
var hostId = CreateMyUniqueIdThatIsTheSameAcrossRestarts();
endpointConfiguration.UniquelyIdentifyRunningInstance()
.UsingCustomIdentifier(hostId);
Accessing ReadOnlySettings
Accessing ReadOnlySettings
using UnicastBus.
is no longer supported as these settings should only be accessed inside features, the pipeline, and the start/stop infrastructure.