How to inject IMessageSession with NServiceBus.Extensions.DependencyInjection?

Using self-hosting (not the Generic Host since projects aren’t completely ported to Core3 yet) and CastleWindsor (to keep existing installer rules).

The documentation have a short paragraph, but I don’t quite understand it:

IMessageSession is not registered automatically in the container and must be registered explicitly to be injected. Access to the session is provided via IStartableEndpointWithExternallyManagedContainer.MessageSession

Code before (using NServiceBus.CastleWindsor):

 	var container = new WindsorContainer();
 	container.Register(Component.For<MyService>().LifestyleSingleton());
 	var endpointConfiguration = new EndpointConfiguration(endpointName);
 	endpointConfiguration.UseContainer<WindsorBuilder>(customizations => { customizations.ExistingContainer(container); });
 	// extra configuration
 	_endpoint = await Endpoint.Start(configuration);
 	container.Register(Component.For<IMessageSession>().Instance(_endpoint).LifestyleSingleton());
 	_service = container.Resolve<MyService>();

Code after (using NServiceBus.Extensions.DependencyInjection):

 	var endpointConfiguration = new EndpointConfiguration(endpointName);
 	// extra configuration
 	var containerConfig = endpointConfiguration.UseContainer(new WindsorServiceProviderFactory());
 	containerConfig.ConfigureContainer(c =>
 	{
 		c.Register(Component.For<MyService>().LifestyleSingleton());
 	});
 	_endpoint = await Endpoint.Start(configuration);
 	// Inject MessageSession? Resolve service?

How to inject additional elements after the service started, also how to manually resolve my service instance?

Shoud I call ConfigureContainer a second time after Endpoint.Start? Seems weird.

    containerConfig.ConfigureContainer(c =>
    {
        c.Register(Component.For<Lazy<IMessageSession>>().Instance(((IStartableEndpointWithExternallyManagedContainer)_endpoint).MessageSession).LifestyleSingleton());

        // Resolve service
        _service = c.Resolve<MyService>();
    });

Hi Dunge,

I would suggest to switch using the externally managed mode to achieve what you want to achieve.

        var endpointConfiguration = new EndpointConfiguration("Sample");
        endpointConfiguration.UseTransport<LearningTransport>();

        var serviceCollection = new ServiceCollection();
        serviceCollection.AddSingleton<MyService>();
        serviceCollection.AddSingleton<MessageSenderService>();

        var endpointWithExternallyManagedContainer = EndpointWithExternallyManagedServiceProvider.Create(endpointConfiguration, serviceCollection);
        // if needed register the session
        serviceCollection.AddSingleton(p => endpointWithExternallyManagedContainer.MessageSession.Value);

        using (var serviceProvider = serviceCollection.BuildServiceProvider())
        {
            var endpoint = await endpointWithExternallyManagedContainer.Start(serviceProvider)
                .ConfigureAwait(false);

            // dependencies that require access to IMessageSession can only be resolved after NServiceBus is started
            var senderService = serviceProvider.GetRequiredService<MessageSenderService>();
            await senderService.SendMessage()
                .ConfigureAwait(false);

            Console.WriteLine("Press any key to exit");
            Console.ReadKey();
            await endpoint.Stop()
                .ConfigureAwait(false);
        }

same would work with the windsor integration, see

        var endpointConfiguration = new EndpointConfiguration("Sample");
        endpointConfiguration.UseTransport<LearningTransport>();

        var serviceCollection = new ServiceCollection();
        var providerFactory = new WindsorServiceProviderFactory();
        serviceCollection.AddSingleton<MyService>();
        serviceCollection.AddSingleton<MessageSenderService>();

        var endpointWithExternallyManagedContainer = EndpointWithExternallyManagedServiceProvider.Create(endpointConfiguration, serviceCollection);
        // if needed register the session
        serviceCollection.AddSingleton(p => endpointWithExternallyManagedContainer.MessageSession.Value);

        var serviceProvider = providerFactory.CreateServiceProvider(providerFactory.CreateBuilder(serviceCollection));
        var endpoint = await endpointWithExternallyManagedContainer.Start(serviceProvider)
            .ConfigureAwait(false);

        // dependencies that require access to IMessageSession can only be resolved after NServiceBus is started
        var senderService = serviceProvider.GetRequiredService<MessageSenderService>();
        await senderService.SendMessage()
            .ConfigureAwait(false);

        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
        await endpoint.Stop()
            .ConfigureAwait(false);

Does that help?

Regards
Daniel

Thanks Daniel,

Yes, I guess that would definitively work.

I’m just a bit confused, doesn’t using the externally managed mode kinda defeat the purpose of having a standard generic interface? Is “my purpose” of wanting to have a MessageSession in order to be able to send messages from a service outside the message handlers really a special case that requires such a different scaffolding?

I realized after writing my original post that the documentation section I copied was (page changed today) from a sub-section of the “externally managed mode” section. I wouldn’t want to influence your answer by introducing the concept if it was not required. But on the other hand, if that’s the way it should be, well thanks, I’ll use your solution.

Hi Dunge

I can relate to your confusion. You are absolutely right that sending messages outside the message handler is something that is common and many of our customers do. During the introduction of the version 6 of NServiceBus we made a conscious choice of splitting the “bus” functionality into two parts. The part in the message handler (handler context) and the part outside the handler (message session). We went down a path of not by default registering it on the container because we felt the danger of accidentally injecting the session into the handler dependencies and the consequences of messages “slipping” out unexpectedly is just too high. The conscious choice of registering things like message sessions is at this stage a host concern.

From the perspective of the host the message session is available because the object returned from Endpoint.Start is also implementing IMessageSession. So therefore anything at the root of the host entry point can get access to it without requiring DI. As far as I understood your example you have some root service that has dependencies to some other things as well as IMessageSession. In this case using the external mode is best because it allows to register the session before the service provider is built and is the way to go because the container doesn’t need to be mutated after the first resolve (which some of the immutable containers don’t support).

Btw. the current complexity comes from NServiceBus having assumed to be the owner of the container for many years. This is no longer true for a while and we are gradually evolving NServiceBus into new directions. Unfortunately those changes sometimes require to introduce different ways of doing things until the larger topic is addressed. We are working on first class support of IServiceCollection and IServiceProvider integration with the core for the next major version and then things should be simpler. Until then I believe for your use case the external mode is the way to go. I will also change the sample on our docs site to better reflect that.

Regards
Daniel

1 Like

We have just updated the docs page

as well as the sample to better guide users

Regards
Daniel

Forget about all my previous replies from today. I did not see there was a difference between EndpointWithExternallyManagedContainer and EndpointWithExternallyManagedServiceProvider

Sorry about that.

1 Like

I confess even I fell into this pitfall!

Hello Daniel!

Since you’ve been so helpful so far, I hope you don’t mind me using this thread for a related but different issue.

I successfully ported all my services to NServiceBus.Extensions.DependencyInjection, some using the external service provider, some not. All in all, it seems to work well for most.

I still have a small issue, and I would like you to look at it and confirm my suspicion that it might be a bug in the libraries. My most used service, and the only one with the PurgeOnStartup(true) configuration (I think that might be the root cause) seems to throw an exception and crash on startup if there’s messages to process in the queue when it starts. After crashing, restarting it right afterwards cause it to starts fine.

Here’s my host start method:

        public async Task Start()
        {
            try
            {
                // Instance Endpoint
                var configuration = EndpointConfigurator.Configuration($"{EndpointName}_{InstanceName}", Properties.Settings.Default.RabbitMqConnectionString, OnCriticalError);

                configuration.AssemblyScanner().ExcludeTypes(new[] { typeof(SharedEventHandlers) });
                configuration.PurgeOnStartup(true);

                // Callback
                configuration.MakeInstanceUniquelyAddressable(InstanceName);
                configuration.EnableCallbacks();

                // Dependency Injection
                var serviceCollection = new ServiceCollection();
                var externalEndpoint = EndpointWithExternallyManagedServiceProvider.Create(configuration, serviceCollection);
                serviceCollection.AddSingleton(p => externalEndpoint.MessageSession.Value);

                var providerFactory = new WindsorServiceProviderFactory();
                var container = providerFactory.CreateBuilder(serviceCollection);
                container.Install(new RepositoriesInstaller());
                container.Register(Component.For<MyService>().LifestyleSingleton());

                var serviceProvider = providerFactory.CreateServiceProvider(container);
                _instanceEndpoint = await externalEndpoint.Start(serviceProvider);

                // Resolve service
                _service = serviceProvider.GetRequiredService<MyService>();
                _service.Start();
            }
            catch (Exception ex)
            {
                FailFast("Failed to start.", ex);
            }
        }

And here’s the exceptions:

License Expiration: 2020-09-20 
2020-08-19 19:01:07.1628 NServiceBus.ReceiveComponent All queues owned by the endpoint will be purged on startup. 
2020-08-19 19:01:12.3112 NServiceBus.RecoverabilityExecutor Immediate Retry is going to retry message '9b729528-2d15-42f6-be0a-ac1c01396b12' because of an exception: System.InvalidOperationException: The message session can only be used after the endpoint is started.
   at NServiceBus.ExternallyManagedContainerHost.<.ctor>b__0_0() in /_/src/NServiceBus.Core/Hosting/ExternallyManagedContainerHost.cs:line 18
   at System.Lazy`1.CreateValue()
   at System.Lazy`1.LazyInitValue()
   at Castle.Windsor.Extensions.DependencyInjection.RegistrationAdapter.<>c__DisplayClass4_0`1.<UsingFactoryMethod>b__0(IKernel kernel)
   at Castle.MicroKernel.ComponentActivator.FactoryMethodActivator`1.Instantiate(CreationContext context)
   at Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.InternalCreate(CreationContext context)
   at Castle.MicroKernel.ComponentActivator.AbstractComponentActivator.Create(CreationContext context, Burden burden)
   at Castle.MicroKernel.Lifestyle.AbstractLifestyleManager.CreateInstance(CreationContext context, Boolean trackedExternally)
   at Castle.MicroKernel.Lifestyle.ScopedLifestyleManager.<>c__DisplayClass4_0.<Resolve>b__0(Action`1 afterCreated)
   at Castle.Windsor.Extensions.DependencyInjection.Scope.ExtensionContainerScope.GetCachedInstance(ComponentModel model, ScopedInstanceActivationCallback createInstance)
   at Castle.MicroKernel.Lifestyle.ScopedLifestyleManager.Resolve(CreationContext context, IReleasePolicy releasePolicy)
   at Castle.MicroKernel.Handlers.DefaultHandler.ResolveCore(CreationContext context, Boolean requiresDecommission, Boolean instanceRequired, Burden& burden)
   at Castle.MicroKernel.Handlers.DefaultHandler.Resolve(CreationContext context, Boolean instanceRequired)
   at Castle.MicroKernel.Resolvers.DefaultDependencyResolver.ResolveFromKernelByType(CreationContext context, ComponentModel model, DependencyModel dependency)
   at Castle.MicroKernel.Resolvers.DefaultDependencyResolver.TryResolveCore(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency, Object& value)
   at Castle.MicroKernel.Resolvers.DefaultDependencyResolver.Resolve(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency)
   at Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.CreateConstructorArguments(ConstructorCandidate constructor, CreationContext context)
   at Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.Instantiate(CreationContext context)
   at Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.InternalCreate(CreationContext context)
   at Castle.MicroKernel.ComponentActivator.AbstractComponentActivator.Create(CreationContext context, Burden burden)
   at Castle.MicroKernel.Lifestyle.AbstractLifestyleManager.CreateInstance(CreationContext context, Boolean trackedExternally)
   at Castle.MicroKernel.Lifestyle.SingletonLifestyleManager.Resolve(CreationContext context, IReleasePolicy releasePolicy)
   at Castle.MicroKernel.Handlers.DefaultHandler.ResolveCore(CreationContext context, Boolean requiresDecommission, Boolean instanceRequired, Burden& burden)
   at Castle.MicroKernel.Handlers.DefaultHandler.Resolve(CreationContext context, Boolean instanceRequired)
   at Castle.MicroKernel.Resolvers.DefaultDependencyResolver.ResolveFromKernelByType(CreationContext context, ComponentModel model, DependencyModel dependency)
   at Castle.MicroKernel.Resolvers.DefaultDependencyResolver.TryResolveCore(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency, Object& value)
   at Castle.MicroKernel.Resolvers.DefaultDependencyResolver.Resolve(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency)
   at Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.CreateConstructorArguments(ConstructorCandidate constructor, CreationContext context)
   at Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.Instantiate(CreationContext context)
   at Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.InternalCreate(CreationContext context)
   at Castle.MicroKernel.ComponentActivator.AbstractComponentActivator.Create(CreationContext context, Burden burden)
   at Castle.MicroKernel.Lifestyle.AbstractLifestyleManager.CreateInstance(CreationContext context, Boolean trackedExternally)
   at Castle.MicroKernel.Lifestyle.ScopedLifestyleManager.<>c__DisplayClass4_0.<Resolve>b__0(Action`1 afterCreated)
   at Castle.Windsor.Extensions.DependencyInjection.Scope.ExtensionContainerScope.GetCachedInstance(ComponentModel model, ScopedInstanceActivationCallback createInstance)
   at Castle.MicroKernel.Lifestyle.ScopedLifestyleManager.Resolve(CreationContext context, IReleasePolicy releasePolicy)
   at Castle.MicroKernel.Handlers.DefaultHandler.ResolveCore(CreationContext context, Boolean requiresDecommission, Boolean instanceRequired, Burden& burden)
   at Castle.MicroKernel.Handlers.DefaultHandler.Resolve(CreationContext context, Boolean instanceRequired)
   at Castle.MicroKernel.DefaultKernel.ResolveComponent(IHandler handler, Type service, Arguments additionalArguments, IReleasePolicy policy, Boolean ignoreParentContext)
   at Castle.MicroKernel.DefaultKernel.Castle.MicroKernel.IKernelInternal.Resolve(Type service, Arguments arguments, IReleasePolicy policy, Boolean ignoreParentContext)
   at Castle.MicroKernel.DefaultKernel.Resolve(Type service, Arguments arguments)
   at Castle.Windsor.Extensions.DependencyInjection.WindsorScopedServiceProvider.GetService(Type serviceType)
   at NServiceBus.Extensions.DependencyInjection.ServiceProviderAdapter.ChildScopeAdapter.Build(Type typeToBuild)
   at NServiceBus.LoadHandlersConnector.<Invoke>d__1.MoveNext() in /_/src/NServiceBus.Core/Pipeline/Incoming/LoadHandlersConnector.cs:line 45
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at NServiceBus.ScheduledTaskHandlingBehavior.<Invoke>d__1.MoveNext() in /_/src/NServiceBus.Core/Scheduling/ScheduledTaskHandlingBehavior.cs:line 22
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at NServiceBus.MutateIncomingMessageBehavior.<InvokeIncomingMessageMutators>d__2.MoveNext() in /_/src/NServiceBus.Core/MessageMutators/MutateInstanceMessage/MutateIncomingMessageBehavior.cs:line 60
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at NServiceBus.DeserializeMessageConnector.<Invoke>d__1.MoveNext() in /_/src/NServiceBus.Core/Pipeline/Incoming/DeserializeMessageConnector.cs:line 33
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at NServiceBus.UnitOfWorkBehavior.<InvokeUnitsOfWork>d__1.MoveNext() in /_/src/NServiceBus.Core/UnitOfWork/UnitOfWorkBehavior.cs:line 40
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at NServiceBus.UnitOfWorkBehavior.<InvokeUnitsOfWork>d__1.MoveNext() in /_/src/NServiceBus.Core/UnitOfWork/UnitOfWorkBehavior.cs:line 62
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at ReceivePerformanceDiagnosticsBehavior.<Invoke>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at NServiceBus.MutateIncomingTransportMessageBehavior.<InvokeIncomingTransportMessagesMutators>d__2.MoveNext() in /_/src/NServiceBus.Core/MessageMutators/MutateTransportMessage/MutateIncomingTransportMessageBehavior.cs:line 59
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at NServiceBus.ProcessingStatisticsBehavior.<Invoke>d__0.MoveNext() in /_/src/NServiceBus.Core/Performance/Statistics/ProcessingStatisticsBehavior.cs:line 25
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at NServiceBus.TransportReceiveToPhysicalMessageConnector.<Invoke>d__1.MoveNext() in /_/src/NServiceBus.Core/Pipeline/Incoming/TransportReceiveToPhysicalMessageConnector.cs:line 39
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at NServiceBus.MainPipelineExecutor.<Invoke>d__1.MoveNext() in /_/src/NServiceBus.Core/Pipeline/MainPipelineExecutor.cs:line 45
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at NServiceBus.Transport.RabbitMQ.MessagePump.<Process>d__29.MoveNext()

and

2020-08-19 19:02:20.7001 MyProject.Host Failed to start. System.InvalidOperationException: The message session can only be used after the endpoint is started.
   at NServiceBus.ExternallyManagedContainerHost.<.ctor>b__0_0() in /_/src/NServiceBus.Core/Hosting/ExternallyManagedContainerHost.cs:line 18
   at System.Lazy`1.CreateValue()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Lazy`1.get_Value()
   at Castle.Windsor.Extensions.DependencyInjection.RegistrationAdapter.<>c__DisplayClass4_0`1.<UsingFactoryMethod>b__0(IKernel kernel)
   at Castle.MicroKernel.ComponentActivator.FactoryMethodActivator`1.Instantiate(CreationContext context)
   at Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.InternalCreate(CreationContext context)
   at Castle.MicroKernel.ComponentActivator.AbstractComponentActivator.Create(CreationContext context, Burden burden)
   at Castle.MicroKernel.Lifestyle.AbstractLifestyleManager.CreateInstance(CreationContext context, Boolean trackedExternally)
   at Castle.MicroKernel.Lifestyle.ScopedLifestyleManager.<>c__DisplayClass4_0.<Resolve>b__0(Action`1 afterCreated)
   at Castle.Windsor.Extensions.DependencyInjection.Scope.ExtensionContainerScope.GetCachedInstance(ComponentModel model, ScopedInstanceActivationCallback createInstance)
   at Castle.MicroKernel.Lifestyle.ScopedLifestyleManager.Resolve(CreationContext context, IReleasePolicy releasePolicy)
   at Castle.MicroKernel.Handlers.DefaultHandler.ResolveCore(CreationContext context, Boolean requiresDecommission, Boolean instanceRequired, Burden& burden)
   at Castle.MicroKernel.Handlers.DefaultHandler.Resolve(CreationContext context, Boolean instanceRequired)
   at Castle.MicroKernel.Resolvers.DefaultDependencyResolver.ResolveFromKernelByType(CreationContext context, ComponentModel model, DependencyModel dependency)
   at Castle.MicroKernel.Resolvers.DefaultDependencyResolver.TryResolveCore(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency, Object& value)
   at Castle.MicroKernel.Resolvers.DefaultDependencyResolver.Resolve(CreationContext context, ISubDependencyResolver contextHandlerResolver, ComponentModel model, DependencyModel dependency)
   at Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.CreateConstructorArguments(ConstructorCandidate constructor, CreationContext context)
   at Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.Instantiate(CreationContext context)
   at Castle.MicroKernel.ComponentActivator.DefaultComponentActivator.InternalCreate(CreationContext context)
   at Castle.MicroKernel.ComponentActivator.AbstractComponentActivator.Create(CreationContext context, Burden burden)
   at Castle.MicroKernel.Lifestyle.AbstractLifestyleManager.CreateInstance(CreationContext context, Boolean trackedExternally)
   at Castle.MicroKernel.Lifestyle.SingletonLifestyleManager.Resolve(CreationContext context, IReleasePolicy releasePolicy)
   at Castle.MicroKernel.Handlers.DefaultHandler.ResolveCore(CreationContext context, Boolean requiresDecommission, Boolean instanceRequired, Burden& burden)
   at Castle.MicroKernel.Handlers.DefaultHandler.Resolve(CreationContext context, Boolean instanceRequired)
   at Castle.MicroKernel.DefaultKernel.ResolveComponent(IHandler handler, Type service, Arguments additionalArguments, IReleasePolicy policy, Boolean ignoreParentContext)
   at Castle.MicroKernel.DefaultKernel.Castle.MicroKernel.IKernelInternal.Resolve(Type service, Arguments arguments, IReleasePolicy policy, Boolean ignoreParentContext)
   at Castle.MicroKernel.DefaultKernel.Resolve(Type service, Arguments arguments)
   at Castle.Windsor.Extensions.DependencyInjection.WindsorScopedServiceProvider.GetRequiredService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at MyProject.Host.<Start>d__8.MoveNext() in C:\TeamCity\buildAgent\work\5b1345fe6501ed74\Sources\MyProject\Host.cs:line 50

The Host.cs:line 50 is the _instanceEndpoint = await externalEndpoint.Start(serviceProvider); line.

Seems like it try to process messages before it really starts?

Hi Dunge

I’m not aware of anything at this stage that would be influenced by PurgeOnStartup. Also by looking at the stack trace I see nothing suspicious at first sight that could cause this.

Would it be possible for you to create a minimal reproduction solution and put it on github somewhere so that I can have a closer look at it?

Regards
Daniel

Hi Daniel,

I’m pretty confused right now. I spent the whole afternoon trying to reproduce this issue on my workstation and it never happens, even with my original project. But as soon as I deploy it to my development machine it always throw the exception on the first service start. I can’t find the difference between my workstation and the dev server, they all have pretty much the same dependencies installed.

I created a minimal solution trying to reproduce the issue, but it doesn’t show up. I started simple, then added parts one by one to reproduce my other project, from Castle Windsor, RabbitMQ, NewtonsoftSerializer, custom routing rules, callbacks. Then I thought it might be the NServiceBus.Heartbeat, NServiceBus.Metrics.ServiceControl or NServiceBus.Metrics.PerformanceCounters that would try to send messages before the endpoint is completely started, but nah nothing. Also tried to build in release mode because that’s pretty much the only difference I could think of between the two environments, but no.

I double checked and nothing in my service could try to send a message from the constructor. I really can’t understand what’s going on. The call stack seems pretty straightforward, the endpoint.Start method try to resolve everything he needs, and end up seeing that the message session is already created so it throw? Do you have a trick I could use like put a breakpoint somewhere to find out what and when the message session object is created/used before endpoint.Start is called?

I even tried to scan your code on GitHub to find out how it works, but can’t say I found anything that would help me.

edit: Oh damn, just after writing this I tried to run as a Windows service instead of the console application (using ServiceBase.Run()) and the exception showed up! But this way, I can’t run it from Visual Studio so I can’t debug and see what goes wrong… Oh well, week’s over, I’m going to fiddle a bit more over this next Monday.

Hi Dunge

I checked the plugins you mentioned and they are all doing raw dispatches and not using the session at all. Weird…

Which version of NServiceBus are you using? 7.4? Have you tried downgrading to a previous version (let’s say 7.2) to verify if it still occurs?

Regards
Daniel

Hi,

Yeah that is something you should probably check to make sure everything is standardized.

I was targeting 7.4, but I tried with 7.2.4 (couldn’t target 7.2.0 because NServiceBus.Extensions.Logging have a >= 7.2.3 rule) and confirm the same problem still occurs.

My last Friday’s message about using ServiceBase to run as a Windows Service was a wrong lead, the exception was about user permission and not the dependency injection problem. Sorry about that.

So I’m still unable to re-create the problem on my development workstation, it still only occurs after deploying on the dev test server. I’m also unable to produce a minimal reproduction solution. So I understand why my error report might be hard to acknowledge from your side.

But unfortunately it’s presently causing a block in our release acceptation workflow preventing me to release a new version in production, which is getting problematic. I might have to revert my NServiceBus.Extensions.DependencyInjection code and return to NServiceBus.CastleWindsor

Hi

Are you by any chance deploying to an existing folder that has another “plugin” type of component in there that tries to access the IMessageSession too early?

Regards
Daniel

Thanks for the hypothesis, and while I did have an old leftover DLL from CastleWindsor that wasn’t needed anymore, it still produce the same result without it and just a clean build.

I checked the .diagnostics folder which seems to list everything the AssemblyScanning discover, and everything listed are my classes, and I can’t find any that would try to access IMessageSession early.

We did have a NLog target that would reroute exceptions and send a message containing the exception, but I disabled it and it’s not the culprit either.

Might be a bad debugging method, but would it be possible to run the code of NServiceBus.Extensions.DependencyInjection from source instead of from the nuget package, and then put a breakpoint/log in the setter of the MessageSession property to see where it comes from?

We embed the source code into the packages as far as I remember so you should be able to set a breakpoint where you want it. You might need to uncheck “Enable Just my Code”

Hello Daniel. Still me with the same problem!

As mentioned before, the issue only seems to happen when ran on a very active server and not on my code workstation, so debugging in VS isn’t cutting it. It also doesn’t happen 100% of the time, but most of it. Good new is I managed to build from source and added a few logs using Console.WriteLine statement in your code to see what’s going on.

Outputting the stacktrace of what try access the IMessageSession early (at the moment it throws) give pretty much the same result as posted above, it’s without a doubt coming from NServiceBus.Transport.RabbitMQ.MessagePump.<Consumer_Received>, which itself is launched from the ThreadPool. And it make sense, since my EventHandler class require my Service class its constructor, and the service class require the MessageSession in his.

The question remained why it try to process messages before the message session is assigned.

If you would please take a look at this file. If I put a log at line 47 just before hostingComponent.Start, I see it getting printed before the exception. But if I put a log at line 49 (between the hostingComponent.Start and the messageSession assignation)­, it never reach it.

My hypothesis is that the hostingComponent.Start actually start the RabbitMQ MessagePump at this moment, and due to race condition it might have time to start processing a message and try to instanciate my EventHandler BEFORE the main thread starting the endpoint assign the messageSession variable.

I’m pretty certain this is a race condition and the assignation should be protected in one way or another to be sure to be assigned before the sub components of NServiceBus begin processing.

Weirdly enough I would have thought adding a Task.Delay or Thread.Sleep at line 49 would force the symptoms to show up all the time, but it doesn’t.

What’s your thoughts?

Hi Dunge

Ok now finally we have the culprit:

since my EventHandler class require my Service class its constructor, and the service class require the MessageSession in his.

This is not a supported scenario. You should not be using IMessageSession in a handler. The reason is simple. IMessageSession is not designed to be used in a handler. It is designed and purposed to be used outside the message handling pipeline. All message operations that you do as part of the message session are immediately going out. For sending messages as part of the handler you need to use the handler context.

See

https://docs.particular.net/nservicebus/messaging/send-a-message#inside-the-incoming-message-processing-pipeline

If the reason why you are injecting IMessageSession is because the service is reused then you might consider using the uniform session package.

But the best way to do this is to slightly restructure your code to not have the service send messages but return information to the handler and that handler actually sending the messages. See our upgrade guides

Hope that helps

Regards
Daniel

That’s not it. Let’s clarify here, every messages sent from a context coming from a thread that is started from an handler always use the IMessageHandlerContext to publish. We never use IMessageSession in a handler, but we do sometime call methods on the service class that also keep and handle on the IMessageSession for other situations.

There are tons of situation where messages need to be sent from outside handlers. The “service” class is a singleton and depending on the endpoint is used for many different things, like for example sending messages on a timer loop, or hosting a WCF service and accepting client requests that end up sending messages. Those need to be send via IMessageSession because they aren’t in a message handler context.

The message handler NEED to interact with the service class. When receiving messages they can sometime update data in the cache, or start new timers, or use it to write files, of whatever. But not directly use it to send messages, just interact with other methods in the singleton services.

I looked at Uniform Session (wasn’t aware of it’s existence), which seems to inject either the IMessageSession or the IMessageHandlerContext depending on the context source. That’s great, but the main safeguard saying that it can’t be used in a cache and can only be injected in services that match the session lifetime kinda prevent me from using it in my service singleton. It’s good in the ASP sample coming from a controller, but not for long living windows services that receive information from outside sources like an udp/tcp server running on a long living service instance. So that’s not a solution.

Starting the endpoint, grabbing its IMessageSession instance and then resolving my service with it after the endpoint was started always worked perfectly in the past. NServiceBus.Extensions.DependencyInjection is the new part of the puzzle that prevent to do that.

Hi Dunge,

Then I got mislead by the following statement of yours.

Excuse me for the misunderstanding.

Another scenario that I can see is that you are getting WCF calls before the endpoint is started and that renders the session to be non-resolvable. That would be something that we have solved as well in the generic host package in the recent release.

How about we schedule a quick call this week to go through some of your scenarios? You can reach me under daniel dot marbach at particular dot net and then we can agree on a good slot to have a call.

Regards
Daniel

Another scenario that I can see is that you are getting WCF calls before the endpoint is started and that renders the session to be non-resolvable.

That can’t be, since the WCF service is included in my own “service” class and started from its “Start” method, which is later in the initialization process. The exception happens at the endpoint start, before I call start on my service. (see code above).

Is there a reason why you are still sure it’s an outside components that would try to use the message session early and not just the internal nservicebus message pump based on the information we have from the call stack?

One thing I had left out was that before those changes my service would get injected in the message handler via a public property setter. After porting to this DI library, it first entered a message handler and the service was still null, so I moved the DI injection from the property setter to the message handler constructor, and that’s when the exception started.

That would be something that we have solved as well in the generic host package in the recent release.

Great. As I started this post with, we are still targeting full framework so we don’t have access to the Microsoft Generic Host yet. I think we might revert and delay the use of this new DI package until we are completely switched to Core3/Net5 which is in our plans. Hopefully with this integration the problem might go away by itself then.

How about we schedule a quick call this week to go through some of your scenarios? You can reach me under daniel dot marbach at particular dot net and then we can agree on a good slot to have a call.

Thanks for the offer. Honestly, while I’m good at writing (often too long) texts on message boards, I’m not as much at ease talking vocally about software design and with the language barrier and all I fear I wouldn’t be able to explain myself clearly in a call.

I was not planning to go in details about our whole solution architecture in the context of this post. But one thing that bother me is how you make it sound like this is impossible:

  • Have a long running worker service class
  • Have this class get the message session injected to publish messages on the bus coming from threads created from events that are external to the message handler.
  • Have a message handler receiving messages from other micro services that would call methods on this worker class.

I understand this makes the message handler and the long running service tightly coupled, but I don’t see other ways of having them interacting. More than half of our micro services follow the same concept (not just the WCF one), and honestly I can’t see how we could have a complex application running any other way. If it is indeed bad design, I’m interested at knowing why, otherwise I hope this is just some misunderstanding.