Home > Back-end >  NServiceBus IUniformSession does not work with Azure WebJobs SDK
NServiceBus IUniformSession does not work with Azure WebJobs SDK

Time:09-05

I am having trouble getting IUniformSession to work in an Azure WebJobs SDK project with Generic Host. I am using .NET built-in DI. I have created a compact repro found below.

I am using

  • NServiceBus 7.7.4
  • NServiceBus.Extensions.Hosting 1.1.0
  • NServiceBus.UniformSession 2.2.0
  • Microsoft.Azure.WebJobs 3.0.33
  • .NET 6

Expected behavior

IUniformSession is resolved as a dependency when classes ("jobs") are instantiated.

Actual behavior

IUniformSession always resolves to NULL. Some quick asserts (as seen below) indicate IUniformSession being registered in the DI container, so the .EnableUniformSession() is doing that correctly, but when an instance is requested it always resolves to NULL.

Steps to reproduce

csproj

<ItemGroup>
    <PackageReference Include="Microsoft.Azure.WebJobs" Version="3.0.33" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions" Version="4.0.1" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="5.0.1" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Host.Storage" Version="4.1.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
    <PackageReference Include="NServiceBus" Version="7.7.4" />
    <PackageReference Include="NServiceBus.Extensions.Hosting" Version="1.1.0" />
    <PackageReference Include="NServiceBus.UniformSession" Version="2.2.0" />
    <PackageReference Include="Serilog" Version="2.11.0" />
    <PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1" />
    <PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
  </ItemGroup>

Program.cs

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NServiceBus;
using NServiceBus.UniformSession;
using NSUniformSessionRepro.DemoClasses;
using Serilog;

// Setup the ability to read config files
var configurationBuilder = new ConfigurationBuilder()
     .SetBasePath(Directory.GetCurrentDirectory())
     // then read the environment variables (in Azure) which will override some settings from the local settings
     .AddEnvironmentVariables();
var configuration = configurationBuilder.Build();

var builder = new HostBuilder();
builder
    .UseSerilog(Log.Logger)
    .ConfigureAppConfiguration((context, config) =>
    {
        config.AddConfiguration(configuration);
    })
    .ConfigureWebJobs(b =>
    {
        b.AddAzureStorageCoreServices()
        .AddAzureStorageBlobs()
        .AddAzureStorageQueues()
        .AddTimers();
    });

IServiceCollection _serviceCollection = null;

builder.ConfigureServices(services =>
{
    services.AddTransient<IMessagingService, MessagingService>();
    _serviceCollection = services;
    
});

builder.UseNServiceBus(context =>
{
    var endpointConfiguration = new EndpointConfiguration("MyEndpoint");

    endpointConfiguration.EnableUniformSession();
    endpointConfiguration.UseTransport<LearningTransport>();
    endpointConfiguration.UsePersistence<LearningPersistence>();
    // configure endpoint here
    return endpointConfiguration;
});


var host = builder.Build();

var isUniformSessionRegistered = _serviceCollection.Any(x => x.ServiceType == typeof(IUniformSession));
Console.WriteLine("UniformSession is registered in DI: "   isUniformSessionRegistered);

using (host)
{
    await host.RunAsync();
}



static void ConfigureLogging(IConfigurationRoot configuration)
{
    var loggerConfiguration = new LoggerConfiguration()
        .MinimumLevel.Information()
        .WriteTo.Console();

    Log.Logger = loggerConfiguration
        .CreateLogger();
}

MessagingService.cs (just a dummy class that itself has a dependency on IUniformSession)

public interface IMessagingService
    {
        // public for testing
        IUniformSession UniformSession { get; set; }
        Task Send(object message);
    }

    public class MessagingService : IMessagingService
    {
        // public for testing
        public IUniformSession UniformSession { get; set; }

        public MessagingService(IUniformSession uniformSession)
        {
            UniformSession = uniformSession;
        }

        public async Task Send(object message)
            => await UniformSession.Send(message);
    }

DemoJob

public class DemoJob
    {
        private readonly IMessagingService _messagingService;

        public DemoJob(IMessagingService messagingService)
        {
            _messagingService = messagingService;
        }

        public void DummyJob([TimerTrigger("0 0 12 * * *", RunOnStartup = true, UseMonitor = false)] TimerInfo timer, TextWriter log)
        {
            // no-op
            // for testing puporses
            if (_messagingService.UniformSession == null)
                Console.Write("UniformSession was NULL");
        }
    }

Any help would be massively appreciated.

CodePudding user response:

This is a bit tricky because of the way the host is setup which causes some problems due to startup order.

First of all, I'm not sure why you build your own abstraction over IUniformSession via IMessagingService so I'll leave that out. Keep in mind that IUniformSession already is an abstraction over IMessageSession (which would be all you need here) and IMessageHandlerContext.

You should be able to take a dependency on IUniformSession or IMessageSession by moving the call to builder.UseNServiceBus before the call to builder.ConfigureWebJobs, e.g.:

builder
    .UseSerilog(Log.Logger)
    .ConfigureAppConfiguration((context, config) =>
    {
        config.AddConfiguration(configuration);
    })
    .UseNServiceBus(context =>
    {
        var endpointConfiguration = new EndpointConfiguration("MyEndpoint");

        endpointConfiguration.EnableUniformSession();
        endpointConfiguration.UseTransport<LearningTransport>();
        endpointConfiguration.UsePersistence<LearningPersistence>();
        // configure endpoint here
        return endpointConfiguration;
    })
    .ConfigureWebJobs(b =>
    {
        b.AddAzureStorageCoreServices()
        .AddAzureStorageBlobs()
        .AddAzureStorageQueues()
        .AddTimers();
    });

This will ensure that NServiceBus will be configured and started before the web jobs related code so that the required services can be injected via DI at the right point in time.

Note that using the IMessageSession should have pointed this out earlier because the NServiceBus generic host support has explicit code to detect such ordering issues that the uniform session does not have.

  • Related