This is part of the NServiceBus Upgrade Guide from Version 5 to 6, which also includes the following individual upgrade guides for specific components:
Feature Details
- Assembly Scanning Changes in NServiceBus Version 6
- No Async Suffix
- Dependency Injection Changes in NServiceBus Version 6
- Deprecated TransportMessage in NServiceBus Version 6
- Endpoint API changes in NServiceBus Version 6
- Extension Seam Changes in NServiceBus Version 6
- Migrate handlers and sagas to Version 6
- Header API changes in NServiceBus Version 6
- Messaging Changes in NServiceBus Version 6
- Moving away from IBus in Version 6
- Recoverability Changes in Version 6
- Serialization Changes in NServiceBus Version 6
- Subscription Changes in NServiceBus Version 6
- Transaction Configuration Changes in NServiceBus Version 6
Transports
- Azure Service Bus Transport (Legacy) Upgrade Version 6 to 7
- RabbitMQ Transport Upgrade Version 3 to 4
- SQL Server Transport Upgrade Version 2 to 3
- SQL Server Transport Upgrade - Supporting Unicode in Headers
Persistence
- Upgrade from NServiceBus Azure Version 6
- NHibernate Persistence Upgrade Version 6 to 7
- NHibernate Persistence - Resolving incorrect timeout table indexes
- RavenDB Persistence Upgrade from 3 to 4
Hosting
Other
- Moving to the DataBus AzureBlobStorage Package
- Azure Cloud Services Host Upgrade Version 6 to 7
- NServiceBus.Azure package deprecated
- Gateway Upgrade Version 1 to 2
- NServiceBus Testing Upgrade Version 5 to 6
- Callback Changes in NServiceBus Version 6
- Migrating the distributor to use sender-side distribution
- Tool and Helper Changes in NServiceBus Version 6
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, in 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. 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 throws 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 are 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 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.
// For NServiceBus version 6.x
var endpointConfiguration = new EndpointConfiguration("EndpointName");
// Custom code before start
var endpointInstance = await Endpoint.Start(endpointConfiguration);
// Custom code after start
// Block the process
// Custom code before stop
await endpointInstance.Stop();
// Custom code after stop
// For NServiceBus version 5.x
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 IMessageSession
or IEndpointInstance
respectively which can be used to send messages during endpoint startup 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.
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.
public class SendAndPublishHandler :
IHandleMessages<MyMessage>
{
public async Task Handle(MyMessage message, IMessageHandlerContext context)
{
await context.Send(new MyOtherMessage());
await context.Publish(new MyEvent());
}
}
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:
var endpointInstance = await Endpoint.Start(endpointConfiguration);
IMessageSession messageSession = endpointInstance;
await messageSession.Send(new SomeMessage());
In this example, a message is being sent during the 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.
When upgrading such custom components to Version 6, it is not safe to replace all instances of 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 Using NServiceBus with ASP.NET MVC for an example how to send from an ASP.NET MVC controller.
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 do 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.
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:
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 });
}
}
}
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:
public class MyContextAccessingDependency
{
public async Task Do(IMessageHandlerContext context)
{
foreach (var changedCustomer in LoadChangedCustomers())
{
await context.Publish(new CustomerChanged { Name = changedCustomer.Name });
}
}
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);
}
}
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:
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.
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.