Migrating NServiceBus from v5 to v6 – Injecting IMessageSession when using NServiceBus.Host

I’m on the way to migrating from NServiceBus v5 to v6, and currently using NServiceBus.Host. As changing everything at the same time would be too much risk, I decided to keep using NServiceBus.Host until the migration to v6 is completed. This post is only relevant if you’re using NServiceBus.Host to host your endpoint.

NServiceBus.Host for v6 does not register the NServiceBus interfaces (e.g. IMessageSession) on the dependency injection container anymore. During startup of the Endpoint you never get access to the endpoint instance to register it on the container. Your only way to access the IMessageSession is by implementing IWantToRunWhenEndpointStartsAndStops. In general, this means you can send messages outside of a message handling context during startup only. But sometimes there is a perfectly valid reason to send messages from outside of a message handler context while the system is running. One example being sending a command after an HTTP API, which you provide to third parties (including other systems inside your company), has been called. Want to know how you can still do this? Read on to find out.

What I want to achieve is, to enable the following simplified piece of API code to send a command.

public class ApiController
{
    public ApiController(IMessageSession session)
    {
        this.session = session;
    }

    public Task SendOrder()
    {
        return this.session.Send(new SendOrderCommand());
    }
}

The NServiceBus team has written a plugin called UniformSession. Why not just use this and inject IUniformSession everywhere and let the plugin figure out whether it should be an IMessageSession or IMessageHandlerContext instance being injected? Because I want the team to write code that meets v6 standards and follows the NServiceBus team’s recommendations. And I don’t want to require a change to any business code once we move away from NServiceBus.Host.

An important aim for my solution is:
The code using the IMessageSession has to be easy to test. e.g. I want to directly inject the IMessageSession into the constructor.

First I will explain the general approach I used to achieve the goal: Send messages outside of a message handler context using IMessageSession. This part will be agnostic of a specific dependency injection container. Then I’m going to explain in detail how I implemented a working solution when using Autofac.

The idea goes as follows:

  1. Implement a MessageSessionHolder to hold onto the IMessageSession instance
  2. Implement IWantToRunWhenEndpointStartsAndStops to store the IMessageSession instance in the MessageSessionHolder during startup
  3. Implement a custom instance resolver for the specific container which uses the MessageSessionHolder to resolve the IMessageSession whenever it needs to.

The MessageSessionHolder has to be a singleton, be it a simple static property or registering the class as a singleton instance on the container. It doesn’t really matter for this case. I find the posted solution the most elegant one though. I will discuss variations of the solution at the end of the post. It’s not a piece of code I would consider writing tests for anyway.

The MessageSessionHolder looks as simple as this

public static class MessageSessionHolder
{
    public static IMessageSession Session { get; private set; }
    public static void SetSession(IMessageSession session) => Session = session;
}

And the class implementing IWantToRunWhenEndpointStartsAndStops looks like this

public class RegisterMessageSessionOnStartup : IWantToRunWhenEndpointStartsAndStops
{
    public Task Start(IMessageSession session)
    {
        MessageSessionHolder.SetSession(session);

        return Task.Completed;
    }

    public Task Stop(IMessageSession session) => Task.CompletedTask;
}

So far the solution is independent of the used dependency injection container.

The final piece that’s missing is the implementation to resolve the IMessageSession at runtime. This kind of extension has to be built specifically for the container you use. I will show you how it works when using Autofac. It should be similar when using a different dependency injection container though.

Autofac has a concept called Registration sources to extend the container for custom resolving.
First you implement your custom registration source like this:

public class MessageSessionRegistrationSource : IRegistrationSource
{
    public IEnumerable RegistrationsFor(Service service, Func<Service, IEnumerable> registrationAccessor)
    {
        var typedService = servie as TypedService;
        if (typedService?.ServiceType != typeof(IMessageSession))
        {
            return Enumerable.Empty();
        }

        return new List
        {
            RegistrationBuilder.ForDelegate((c, p) => MessageSessionHolder.Session).CreateRegistration()
        };
    }

    public bool IsAdapterForIndividualComponents => false;
}

Then register the source on the ContainerBuilder like this:

var builder = new ContainerBuilder();
builder.RegisterSource(new MessageSessionRegistrationSource());

This code will be placed in the class implementing IConfigureThisEndpoint.

We’re done. We have a simple solution to gain access to IMessageSession while still using NServiceBus.Host. When time has come for us to move away from NServiceBus.Host to do self-hosting the change is going to be as easy as removing above code and registering IMessageSession on the container as this is simple when self-hosting.

If you’re happy with this exact solution you can stop reading here. Please leave a comment if this solution helped you or you have any questions or remarks about it.

For the rest of this post I will discuss two minor variations which might be preferable/easier for you.

Variation A
Instead of having MessageSessionHolder being a static implementation, you could choose to implement it non-static, basically just remove the three static keywords:

public class MessageSessionHolder
{
    public IMessageSession Session { get; private set; }
    public void SetSession(IMessageSession session) => Session = session;
}

Additionally you need to:
– register it as a single instance on your container
– inject it into the constructor of RegisterMessageSessionOnStartup

This variation is absolutely equivalent with the one above. I just prefer to not have to register this class on the container, as it is a little bit hidden, and can be tedious to figure out if it’s forgotten.

Variation B
If you struggle to build a similar construct as the MessageSessionRegistrationSource for the container you are using, I suggest you go with the following solution.
Add two interfaces IProvideMessageSession and IHostMessageSession.

public interface IProvideMessageSession
{
    IMessageSession Session { get; }
}

public interface IHostMessageSession
{
    void SetSession(IMessageSession session);
}

Implement the MessageSessionHolder like this:

public class MessageSessionHolder : IProvideMessageSession, IHostMessageSession
{
    public IMessageSession Session { get; private set; }
    public void SetSession(IMessageSession session) => Session = session;
}

Inject IHostMessageSession into RegisterMessageSessionOnStartup and use it to store the IMessageSession

public class RegisterMessageSessionOnStartup : IWantToRunWhenEndpointStartsAndStops
{
    public RegisterMessageSessionOnStartup(IHostMessageSession sessionHost)
    {
        this.sessionHost = sessionHost;
    }

    public Task Start(IMessageSession session)
    {
        this.sessionHost.SetSession(session);

        return Task.Completed;
    }

    public Task Stop(IMessageSession session) => Task.CompletedTask;
}

Register MessageSessionHolder as a single instance on your container and make sure both interfaces are registered to the same instance.

Inject IProvideMessageSession into the code that needs it. In the example I used here this would look like this:

public class ApiController
{
    public ApiController(IProvideMessageSession sessionProvider)
    {
        this.sessionProvider = sessionProvider;
    }

    public Task SendOrder()
    {
        return this.sessionProvider.Session.Send(new SendOrderCommand());
    }
}

This variation has a clear drawback which is that you can’t simply inject IMessageSession where you need it. But at least it allows you to use the IMessageSession where needed if you can’t customize your dependency injection container. I’m happy about your comments, especially if you find a better solution.

About the author

Philipp Dolder

1 comment

  • Hi Philipp

    Thanks for the write-up. We are aware that the situation with the NServiceBus.Host is not ideal. I would even go so far to say that the whole dependency injection story with message session is a bit messy due to the conforming container that we use. We plan to address this in a gradual way step by step. Let’s go back to the Host for a second. One of the reasons the host exists is to provide a bus driven entry point into the system which allows to run it as console for development and testing and as Windows Service in production. Over the years we realized that the host comes with many drawbacks such as needing an “alien way” of doing assembly redirects. With the rise of multiple hosting models and the simplified configuration API that NServiceBus v5 and later provides we realized that there is less and less need for the host. And using the host makes things more complicated than useful.

    Consider the following scenario. You have a Windows Service that hosts the bus and an API endpoint like in this blog post. WebApi comes with its hosting abstractions. Windows services have a relatively simple and straightforward base class that can be implemented, and you are ready to go. We even provide a bootstrapping package for Windows Services (https://docs.particular.net/nservicebus/hosting/windows-service). With that in mind, you can create an endpoint configuration that starts an endpoint and then passes the message session instance to the WebApi hosting abstraction. With that, you can even have two separate containers (one for the bus stuff and one for the API stuff) if required.

    There is not much value left in the Host if you consider this. You pointed out the risk factor when changing too many things at the same time. That is a good point. But on the other hand, you now need to maintain infrastructure to rebind message session, and you are exposing yourself to the problem that someone could inject IMessageSession into a component that is injected into a handler, and suddenly you have ghost messages in your system. So is it so risky to change the host to self-hosting?

    Regards
    Daniel

By Philipp Dolder

Recent Posts