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.