Endpoint hosting with the Generic Host

Component: NServiceBus
NuGet Package NServiceBus (7.x)

The sample uses the Generic Host and the Microsoft.Extensions.Hosting.WindowsServices NuGet package to host NServiceBus as a Windows Service using the Generic Host underneath.

Prerequisites

The sample has the following prerequisites:

Running the sample as a console

In Visual Studio, press F5 to start the sample as a console application.

Running the sample as a Windows Service

  • Install PowerShell Core on Windows
  • Start PowerShell core with elevated permissions
  • Run dotnet publish in the directory of the sample; for example: C:\samples\generic-host
  • Run New-Service -Name WorkerTest -BinaryPathName "C:\samples\generic-host\bin\Debug\netcoreapp3.1\publish\GenericHost.exe"
  • Run Start-Service WorkerTest
  • Go to the Event Viewer under Windows Logs\Applications and observe event log entries from source GenericHost with the following content
Category: MyMessageHandler
EventId: 0

Received message #{Number}
  • Once done, run Stop-Service WorkerTest and Remove-Service WorkerTest

Code walk-through

var builder = Host.CreateDefaultBuilder(args);
builder.UseWindowsService();

The snippet above shows how the host builder runs by default as a Windows Service. If the sample is started with the debugger attached, it uses the console's lifetime instead. To always use the console lifetime use the following code instead:

var builder = Host.CreateDefaultBuilder(args);
builder.UseConsoleLifetime();
builder.UseMicrosoftLogFactoryLogging();
builder.ConfigureLogging((ctx, logging) =>
{
    logging.AddConfiguration(ctx.Configuration.GetSection("Logging"));

    logging.AddEventLog();
    logging.AddConsole();
});

To enable integration with Microsoft.Extensions.Logging, the NServiceBus.MicrosoftLogging.Hosting community package is used and configured in combination with the standard logging.

Next, the builder configures NServiceBus using the NServiceBus.Extensions.Hosting package, including the critical error action that will shut down the application or service in case of a critical error.

builder.UseNServiceBus(ctx =>
{
    var endpointConfiguration = new EndpointConfiguration("Samples.Hosting.GenericHost");
    endpointConfiguration.UseTransport<LearningTransport>();

    endpointConfiguration.DefineCriticalErrorAction(OnCriticalError);

    return endpointConfiguration;
});

The critical error action:

private static async Task OnCriticalError(ICriticalErrorContext context)
{
    var fatalMessage =
        $"The following critical error was encountered:{Environment.NewLine}{context.Error}{Environment.NewLine}Process is shutting down. StackTrace: {Environment.NewLine}{context.Exception.StackTrace}";
    EventLog.WriteEntry(".NET Runtime", fatalMessage, EventLogEntryType.Error);

    try
    {
        await context.Stop().ConfigureAwait(false);
    }
    finally
    {
        Environment.FailFast(fatalMessage, context.Exception);
    }
}

To simulate work, a BackgroundService called Worker is registered as a hosted service:

return builder.ConfigureServices(services => { services.AddHostedService<Worker>(); });

The worker takes a dependency on IServiceProvider in order to retrieve the message session. This is required because all hosted services are resolved from the container first and then started in the order of having been added. Therefore it is not possible to inject IMessageSession directly because the hosted service that starts the NServiceBus endpoint has not been started yet when the worker service constructor is being resolved from the container.

class Worker : BackgroundService
{
    private readonly IServiceProvider provider;

    public Worker(IServiceProvider provider)
    {
        this.provider = provider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            var session = provider.GetService<IMessageSession>();

            var number = 0;
            while (!stoppingToken.IsCancellationRequested)
            {
                await session.SendLocal(new MyMessage {Number = number++})
                    .ConfigureAwait(false);

                await Task.Delay(1000, stoppingToken).ConfigureAwait(false);
            }
        }
        catch (OperationCanceledException)
        {
            // graceful shutdown
        }
    }
}

Related Articles

  • Assembly scanning
    To enable automatic detection of various features NServiceBus scans assemblies for well known types.
  • Hosting
    Describes the various approaches to endpoint hosting.

Last modified