NServiceBus and .NET Core Generic Host

My current client is using .NET Core 2.x, with plans to upgrade to 3.x next month. As part of that system, we do quite a bit of messaging, with NServiceBus as the tool of choice to help make this easier. To get it working with our .NET Core 2.x applications, we did quite a bit of what I laid out in my Messaging Endpoints in Azure series.

Since then, NServiceBus released first-class support for the .NET Core Generic Host, which underwent a fairly large refactoring in the 2.x to 3.0 timeframe. Andrew Lock's post goes into more detail, but the gist of it is, NServiceBus has first-class support for .NET Core 3.x and later.

What that means for us is hosting NServiceBus inside a .NET Core application couldn't be easier. The NServiceBus.Extensions.Hosting package provides all the integration we need to add a hosted NServiceBus service and integrate with the built-in DI container.

Configuring NServiceBus

With any kind of hosted .NET Core application (Console, ASP.NET Core, Worker), we just need to add the extensions package:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="NServiceBus.Extensions.Hosting" Version="1.0.0" />

And add the configuration directly off of the host builder:

Host.CreateDefaultBuilder(args)
    .UseNServiceBus(hostBuilderContext =>
    {
        var endpointConfiguration = new EndpointConfiguration("WebApplication");

        // configure endpoint here

        return endpointConfiguration;
    })
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.UseStartup<Startup>();
    });

Or with a Worker SDK:

<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.2" />

It's really not much different:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .UseNServiceBus(hostBuilderContext =>
        {
            var endpointConfiguration = new EndpointConfiguration("WorkerService");

            // configure endpoint here

            return endpointConfiguration;
        });

And our endpoint is up and running.

Logging and Serialization

We're not quite there yet, though. The out-of-the-box serialization is XML (which is fine by me), but many folks prefer JSON. Additionally, the logging support inside of NServiceBus is not currently integrated with this package. For serialization, we can use the new System.Text.Json support instead of Newtonsoft.Json.

We'll pull in the community packages from Simon Cropp:

With those two packages in place, we can configure our host's serializer and logging:

Host.CreateDefaultBuilder(args)
    .UseMicrosoftLogFactoryLogging()
    .UseNServiceBus(hostBuilderContext =>
    {
        var endpointConfiguration = new EndpointConfiguration("WorkerService");

        endpointConfiguration.UseSerialization<SystemJsonSerializer>();

We now have integrated logging, hosting, and dependency injection with anything that uses the generic host.

Using the logger

Now in our handlers, we can add dependencies directly on the Microsoft logger,  ILogger<T>:

public class SaySomethingHandler : IHandleMessages<SaySomething>
{
    private readonly ILogger<SaySomethingHandler> _logger;

    public SaySomethingHandler(ILogger<SaySomethingHandler> logger) 
        => _logger = logger;

    public Task Handle(SaySomething message, IMessageHandlerContext context)
    {
        _logger.LogInformation("Saying {message}", message.Message);

        return context.Reply(new SaySomethingResponse
        {
            Message = $"Back at ya {message.Message}"
        });
    }
}

And we get an integrated logging experience:

info: NServiceBus.LicenseManager[0]
      Selected active license from C:\Users\jbogard\AppData\Local\ParticularSoftware\license.xml
      License Expiration: 2020-06-16
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Users\jbogard\source\repos\NsbActivities\WorkerService
info: WorkerService.SaySomethingHandler[0]
      Saying Hello World!

Now with this logging and dependency injection integration, we can use any logger or container that extends the built-in abstractions. My current client (and most) use Serilog, which makes it very easy to plug in to the generic host as well.

With these packages, we'll be able to delete a lot of infrastructure code that wasn't adding any value, which is always a good thing.