Getting Started
Architecture
NServiceBus
Transports
Persistence
ServiceInsight
ServicePulse
ServiceControl
Monitoring
Samples

Upgrade Version 8 to 9

Component: NServiceBus

Removed support for .NET Framework

NServiceBus 9 no longer supports any version of the .NET Framework. Instead, it targets .NET 8 only (Read more about the supported frameworks and platforms). Any component in NServiceBus 8 that is .NET Framework only (for example, the MSMQ transport)will not have a version that is compatible with NServiceBus 9. NServiceBus 8 will continue to be supported for use on the .NET Framework.

Serializer choice is now mandatory

The XML serializer is no longer the default serializer, so a serializer must always be configured.

SendOptions immediate dispatch changes

The method used to determine if immediate dispatch has been requested for a message has been renamed.

9.x NServiceBus
class MyBehavior : Behavior<IOutgoingContext>
{
    public override Task Invoke(IOutgoingContext context, Func<Task> next)
    {
        var sendOptions = context.Extensions.Get<SendOptions>();

        if (sendOptions.IsImmediateDispatchSet())
        {
            // do something
        }

        return next();
    }
}
8.x NServiceBus
class MyBehavior : Behavior<IOutgoingContext>
{
    public override Task Invoke(IOutgoingContext context, Func<Task> next)
    {
        var sendOptions = context.Extensions.Get<SendOptions>();

        if (sendOptions.RequiredImmediateDispatch())
        {
            // do something
        }

        return next();
    }
}

IManageUnitsOfWork has been removed

The IManageUnitsOfWork API has been removed. Instead, a pipeline behavior should be used to implement custom units of work.

9.x NServiceBus
class MyUnitOfWork : Behavior<IIncomingPhysicalMessageContext>
{
    public override async Task Invoke(IIncomingPhysicalMessageContext context, Func<Task> next)
    {
        // start the custom unit of work

        try
        {
            await next();
        }
        catch (Exception ex)
        {
            // handle exception
        }

        // end the custom unit of work
    }
}
8.x NServiceBus
class MyUnitOfWork : IManageUnitsOfWork
{
    public Task Begin()
    {
        // start the custom unit of work

        return Task.CompletedTask;
    }

    public Task End(Exception ex = null)
    {
        // end the custom unit of work

        if (ex != null)
        {
            // handle exception
        }

        return Task.CompletedTask;
    }
}

API to override machine name has changed

The RuntimeEnvironment.MachineNameAction API Override the machine name has been removed. The replacement API is the HostInfoSettings.UsingHostName method.

9.x NServiceBus
var endpointConfiguration = new EndpointConfiguration("MyEndpoint");

endpointConfiguration.UniquelyIdentifyRunningInstance()
    .UsingHostName("MyMachineName");
8.x NServiceBus
RuntimeEnvironment.MachineNameAction = () => "MyMachineName";

API to set additional audit metadata has changed

The API to set additional audit metadata has been changed.

9.x NServiceBus
public class MyAuditDataBehavior : Behavior<IAuditContext>
{
    public override Task Invoke(IAuditContext context, Func<Task> next)
    {
        context.AuditMetadata["myKey"] = "MyValue";
        return next();
    }
}
8.x NServiceBus
public class MyAuditDataBehavior : Behavior<IAuditContext>
{
    public override Task Invoke(IAuditContext context, Func<Task> next)
    {
        context.AddAuditData("myKey","MyValue");
        return next();
    }
}

Dependency registration access in features renamed

The property used to access container registrations in features has been renamed from Container to Services.

9.x NServiceBus
protected override void Setup(FeatureConfigurationContext context)
{
    context.Services.AddSingleton<MySingleton>();
}
8.x NServiceBus
protected override void Setup(FeatureConfigurationContext context)
{
    context.Container.AddSingleton<MySingleton>();
}

Service collection extensions for backward compatibility removed

Service collection extensions to ease the transition to Microsoft DI abstractions have been removed. It is now required to use the registration APIs added in NServiceBus 8.

9.x NServiceBus
// 1
services.Add(new ServiceDescriptor(typeof(MyDependency), typeof(MyDependency), ServiceLifetime.Singleton));
// 2
services.AddSingleton<MyDependency>();
// 3
services.AddScoped<MyDependency>();
// 4
services.AddTransient<MyDependency>();
// 5
services.AddSingleton(new MyDependency());
// 6
if (services.AsEnumerable().Any(serviceDescriptor => serviceDescriptor.ServiceType == typeof(MyDependency)))
{
    // do something
}
8.x NServiceBus
// 1
services.ConfigureComponent(typeof(MyDependency), DependencyLifecycle.SingleInstance);
// 2
services.ConfigureComponent<MyDependency>(DependencyLifecycle.SingleInstance);
// 3
services.ConfigureComponent<MyDependency>(DependencyLifecycle.InstancePerUnitOfWork);
// 4
services.ConfigureComponent<MyDependency>(DependencyLifecycle.InstancePerCall);
// 5
services.RegisterSingleton(new MyDependency());
// 6
if(services.HasComponent<MyDependency>())
{
    // do something
}

Endpoint addresses

In NServiceBus version 8 and earlier, the local transport-specific queue addresses are accessible via the settings.LocalAddress() and settings.InstanceSpecificQueue() settings extension methods. These extension methods have been replaced with a variety of new APIs, depending on the scenario of where the addresses are needed.

Accessing logical addresses in features

Since endpoint addresses are translated to transport-specific ones later during endpoint startup, addresses are defined using a transport-agnostic QueueAddress type. The addresses can be accessed via the FeatureConfigurationContext:

9.x NServiceBus
class MyFeature : Feature
{
    protected override void Setup(FeatureConfigurationContext context)
    {
        QueueAddress local = context.LocalQueueAddress();
        QueueAddress instance = context.InstanceSpecificQueueAddress();
    }
}
8.x NServiceBus
class MyFeature : Feature
{
    protected override void Setup(FeatureConfigurationContext context)
    {
        string local = context.Settings.LocalAddress();
        string instance = context.Settings.InstanceSpecificQueue();
    }
}

Accessing the endpoint's receive addresses

Inject the ReceiveAddresses type to access the endpoint's receive addresses.

class StartupTask(ReceiveAddresses receiveAddresses) : FeatureStartupTask
{
    protected override Task OnStart(IMessageSession session, CancellationToken cancellationToken = default)
    {
        // equivalent to settings.LocalAddress()
        Console.WriteLine($"Starting endpoint, listening on {receiveAddresses.MainReceiveAddress}");

        if (receiveAddresses.InstanceReceiveAddress != null)
        {
            // equivalent to settings.InstanceSpecificQueue())
            Console.WriteLine($"Starting endpoint, listening on {receiveAddresses.InstanceReceiveAddress}");
        }
        return Task.CompletedTask;
    }

    protected override Task OnStop(IMessageSession session, CancellationToken cancellationToken = default) => Task.CompletedTask;
}

Dynamic address translation

Instead of using settings.Get<TransportDefinition>().ToTransportAddress(myAddress), inject the ITransportAddressResolver type to translate a QueueAddress to a transport-specific address at runtime.

9.x NServiceBus
public class MyHandler(ITransportAddressResolver addressResolver) : IHandleMessages<MyMessage>
{
    public Task Handle(MyMessage message, IMessageHandlerContext context)
    {
        var destination = addressResolver.ToTransportAddress(new QueueAddress("Sales"));
        var sendOptions = new SendOptions();
        sendOptions.SetDestination(destination);
        return context.Send(new SomeMessage(), sendOptions);
    }
}
8.x NServiceBus
public class MyHandler : IHandleMessages<MyMessage>
{
    readonly IReadOnlySettings settings;

    public MyHandler(IReadOnlySettings settings)
    {
        this.settings = settings;
    }

    public Task Handle(MyMessage message, IMessageHandlerContext context)
    {
        var destination = settings.Get<TransportDefinition>().ToTransportAddress(new QueueAddress("Sales"));
        var sendOptions = new SendOptions();
        sendOptions.SetDestination(destination);
        return context.Send(new SomeMessage(), sendOptions);
    }
}

Extensibility

This section describes changes to advanced extensibility APIs.

Making features depend on message driven subscriptions

The API to make features depend on message-driven subscriptions when implementing custom persisters has changed:

9.x NServiceBus
class MyFeature : Feature
{
    public MyFeature() => DependsOn("NServiceBus.Features.MessageDrivenSubscriptions");

    protected override void Setup(FeatureConfigurationContext context)
    {
        // setup my feature
    }
}
8.x NServiceBus
class MyFeature : Feature
{
    public MyFeature() => DependsOn<MessageDrivenSubscriptions>();

    protected override void Setup(FeatureConfigurationContext context)
    {
        // setup my feature
    }
}

The extension point for event-based notifications has been removed

NServiceBus 8 already replaced the event-based error notifications with task-based callbacks. The extension point for custom event-based notifications has been removed in NServiceBus 9. Any custom notifications should be converted. See error notification events for more details.