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
The handler method on IHandleMessages
now returns a Task. To leverage async code, add the async
keyword to the handler method and use await
for async methods. To convert the synchronous code add return Task.
or return Task.
(.NET 4.6 and higher) to the handler methods.
Do not return null
from the message handlers. Returning null
will result in an Exception.
// For NServiceBus version 6.x
public class UpgradeMyAsynchronousHandler :
IHandleMessages<MyMessage>
{
public Task Handle(MyMessage message, IMessageHandlerContext context)
{
return SomeLibrary.SomeAsyncMethod(message);
}
}
public class UpgradeMySynchronousHandler :
IHandleMessages<MyMessage>
{
public Task Handle(MyMessage message, IMessageHandlerContext context)
{
// when no asynchronous code is executed in a handler
// Task.CompletedTask can be returned
SomeLibrary.SomeMethod(message.Data);
return Task.CompletedTask;
}
}
// For NServiceBus version 5.x
public class UpgradeMessageHandler :
IHandleMessages<MyMessage>
{
public void Handle(MyMessage message)
{
SomeLibrary.SomeMethod(message.Data);
}
}
API Changes
Bus Send and Receive
There is a change in the parameters, giving access to the IMessageHandlerContext
, which provides the methods that used to be called from IBus
. Use the IMessageHandlerContext
to send and publish messages.
public class SendAndPublishHandler :
IHandleMessages<MyMessage>
{
public async Task Handle(MyMessage message, IMessageHandlerContext context)
{
await context.Send(new MyOtherMessage());
await context.Publish(new MyEvent());
}
}
Message handler ordering
In Version 6 the message handler ordering APIs are simplified. The full API can be seen in Handler ordering.
Specifying a Handler to run first
// For NServiceBus version 6.x
endpointConfiguration.ExecuteTheseHandlersFirst(typeof(HandlerB));
// For NServiceBus version 5.x
public class MySpecifyingFirst :
ISpecifyMessageHandlerOrdering
{
public void SpecifyOrder(Order order)
{
order.SpecifyFirst<HandlerB>();
}
}
Specifying Handler order
// For NServiceBus version 6.x
endpointConfiguration.ExecuteTheseHandlersFirst(
typeof(HandlerB),
typeof(HandlerA),
typeof(HandlerC));
// For NServiceBus version 5.x
busConfiguration.LoadMessageHandlers(First<HandlerB>.Then<HandlerA>().AndThen<HandlerC>());
New context arguments
The signature for the mutators now passes context arguments that give access to relevant information on the message and also on the mutation of the message. This context gives access to the same functionality as previous versions so update the code accordingly.
See header manipulation for one example on how this might look.
HandleCurrentMessageLater and the Outbox
The HandleCurrentMessageLater
method can no longer be used in conjunction with the Outbox.
When this scenario is detected an exception with the following message is thrown:
HandleCurrentMessageLater cannot be used in conjunction with the Outbox. Use the recoverability mechanisms or delayed delivery instead.
Use the recoverability or delayed delivery APIs instead when using the Outbox.
Migration
In previous versions of NServiceBus, a typical handler class looks like the below:
public class MigrationBeginning :
IHandleMessagesFromPreviousVersions<MyMessage>
{
public IBus Bus { get; set; }
public void Handle(MyMessage message)
{
Bus.Send(new MyOtherMessage());
Bus.Publish(new MyEvent());
}
}
Implement the new IHandleMessages
interface. Remove any code that is generated by the IDE or additional tools like Resharper and mark the new Handle
method as async
by adding the async
keyword.
public class MigrationStep1 :
IHandleMessages<MyMessage>
{
public IBus Bus { get; set; }
public void Handle(MyMessage message)
{
Bus.Send(new MyOtherMessage());
Bus.Publish(new MyEvent());
}
public async Task Handle(MyMessage message, IMessageHandlerContext context)
{
}
}
Rename the bus property or the bus constructor parameter to context
. Although the IBus interface has been deprecated and can be safely removed, for this step renaming the property instead of deleting it comes in useful when refactoring existing code. It's much better to rename all the existing references from bus
property to context
.
public class MigrationStep2 :
IHandleMessages<MyMessage>
{
public IBus context { get; set; }
public void Handle(MyMessage message)
{
context.Send(new MyOtherMessage());
context.Publish(new MyEvent());
}
public async Task Handle(MyMessage message, IMessageHandlerContext context)
{
}
}
Inline or "cut-and-paste" the old Handle
method code into the new asynchronous Handle
method. When the code is compiled a compiler warning CS4014
will be shown indicating that there are asynchronous methods in the handler which are not awaited.
public class MigrationStep3 :
IHandleMessages<MyMessage>
{
public async Task Handle(MyMessage message, IMessageHandlerContext context)
{
// CS4014: Consider applying the 'await' operator to the result of the call.
context.Send(new MyOtherMessage());
context.Publish(new MyEvent());
}
}
Fix the compiler warnings by introducing the await
statement to each asynchronous method call.
Visual Studio 2015 and higher has the capability to automatically fix those warnings with the Ctrl+.
(depending on the keybindings) shortcut. It is even possible to fix it in the whole solution if desired.
public class MigrationStep4 :
IHandleMessages<MyMessage>
{
public async Task Handle(MyMessage message, IMessageHandlerContext context)
{
await context.Send(new MyOtherMessage());
await context.Publish(new MyEvent());
}
}
After these steps start moving other code in the handler towards async if the code supports it when it is desired to fully leverage async/await. For example with Entity Framework instead of calling SaveChanges
call SaveChangesAsync
on the database context.
For information about how to migrate handlers with dependencies that access the IBus
interface, refer to IBus interface has been deprecated guidance.
Saga API Changes
Remove NServiceBus.Saga namespace
The NServiceBus.
namespace has been removed to stop it clashing with the NServiceBus.
class. For all commonly used APIs (e.g., the Saga
class and IContainSagaData
interface) they have been moved into the NServiceBus
namespace. Other more advanced APIs (e.g., the IFinder
and IHandleSagaNotFound
interfaces) have been moved into the NServiceBus.
namespace.
In most cases using NServiceBus.
can be replaced with using NServiceBus
.
Unique attribute no longer needed
NServiceBus automatically makes the correlated saga property unique without the need for an explicit [Unique]
attribute to be used. This attribute can be safely removed from saga data types.
ConfigureHowToFindSaga
All messages that start the saga (IAmStartedByMessages
) need to be mapped to the saga data using either a mapping in ConfigureHowToFindSaga
method, or a custom saga finder; otherwise, an exception is thrown on endpoint startup. Other messages that are handled by the saga (IHandleMessages
) also require mappings, unless they are reply messages resulting from a message sent out of the saga, in which case they contain the SagaId in a message header. Messages that cannot be mapped by a SagaId message header, by a property mapping in ConfigureHowToFindSaga
, or via a custom saga finder will throw a runtime exception.
In the below example, the OrderSaga
is started by the StartOrder
message. The OrderSaga
also handles the CompleteOrder
message.
public class OrderSaga :
Saga<OrderSagaData>,
IAmStartedByMessages<StartOrder>,
IHandleMessages<CompleteOrder>
In Version 6, the StartOrder
message needs to be specified in the ConfigureHowToFindSaga
method.
protected override void ConfigureHowToFindSaga(SagaPropertyMapper<OrderSagaData> mapper)
{
mapper.ConfigureMapping<StartOrder>(message => message.OrderId)
.ToSaga(sagaData => sagaData.OrderId);
mapper.ConfigureMapping<CompleteOrder>(message => message.OrderId)
.ToSaga(sagaData => sagaData.OrderId);
}
Correlating properties
Version 6 automatically correlates incoming message properties to its saga data counterparts. Any saga data correlation in the message handler code can be safely removed. Correlated properties (for existing saga instances) do not change once set.
Correlated properties must have a non default value, i.e., not null and not empty, assigned when persisted. If not the following exception is thrown:
The correlated property 'MyPropery' on Saga 'MySaga' does not have a value.
A correlated property must have a non-default (i.e., non-null and non-empty) value assigned when a new saga instance is created.
Use a custom finder for the received message to override this validation.
// For NServiceBus version 6.x
public async Task Handle(StartOrder message, IMessageHandlerContext context)
{
// The processing logic for the StartOrder message
}
// For NServiceBus version 5.x
public void Handle(StartOrder message)
{
Data.OrderId = message.OrderId;
// The processing logic for the StartOrder message
}
Versions 6 and above only support correlating messages to a single saga property. Correlating on more than one property is still supported by creating a custom saga finder. If sagas with multiple correlations mappings to different properties are detected the following exception is thrown:
Sagas can only have mappings that correlate on a single saga property. Use custom finders to correlate *message types* to Saga *saga type*
Saga persisters & finders
Saga persisters (ISagaPersister
) and finders (IFindSagas
) introduce a new parameter SagaPersistenceOptions
. This parameter gives access to the saga metadata and pipeline context. The options enable persisters and finders to manipulate everything that exists in the context during message pipeline execution. For more information see Sagas and Complex saga finding logic.
MarkAsComplete no longer virtual
The Saga
base class method MarkAsComplete
is no longer virtual.
RequestTimeout requires IMessageHandlerContext
RequestTimeout
requires a IMessageHandlerContext
as additional parameter. Pass the context argument received in the handle method to RequestTimeout
.
ReplyToOriginator requires IMessageHandlerContext
ReplyToOriginator
requires a IMessageHandlerContext
as additional parameter. Pass the context argument received in the handle method to RequestTimeout
.