Featured image of post Modern minimal workers in .NET

Modern minimal workers in .NET

Learn how to create minimal workers in .NET and leverage the full potential of .NET extensions libraries for your services and applications.

Since the release of .NET 6, we’ve heard a lot about ASP.NET Core minimal APIs. They’ve been discussed at Microsoft conferences, in blog posts, in YouTube videos, and on social networks. We’ve all seen this kind of code sample of a minimal API:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");
app.Run();

This is significantly more concise than controller-based web APIs, and we can all agree on that. But did you know that since the release of .NET 7, there is also a way to create minimal workers?

A minimal worker console application looks like this:

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<MyWorkerService>();

var app = builder.Build();
app.Run();

Creating a minimal worker in .NET

Create an empty console application and reference the Microsoft.Extensions.Hosting package, at least version 7.0.0 (Host.CreateApplicationBuilder() has been added in this version).

You can also reference the Microsoft.NET.Sdk.Worker SDK in your project file, which is actually a dependency of the Microsoft.NET.Sdk.Web SDK, as mentioned in the project’s README. Referencing the Microsoft.NET.Sdk.Worker provides some benefits such as:

  • *.json (like appsettings.json) files are automatically copied to the output and publish directories.
  • A few Microsoft.Extensions.* using directives are automatically added only if you enable implicit usings.

So your project file should look like this:

<Project Sdk="Microsoft.NET.Sdk.Worker">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0"/> <!-- or later -->
  </ItemGroup>
</Project>

You can even target .NET Framework:

<Project Sdk="Microsoft.NET.Sdk.Worker">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net462</TargetFramework>
    <LangVersion>10</LangVersion> <!-- required for top-level statements on .NET Framework -->
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0"/> <!-- or later -->
  </ItemGroup>
</Project>

Host.CreateApplicationBuilder() returns aHostApplicationBuilder, which shares some similarities with the WebApplicationBuilder used in ASP.NET Core minimal APIs. You are probably already familiar with these properties:

Here’s an example where all of these properties are being used:

var builder = Host.CreateApplicationBuilder(args);

if (!builder.Environment.IsDevelopment())
{
    builder.Configuration.AddAzureKeyVault(new Uri("https://mykeyvault"), new DefaultAzureCredential());
}

builder.Logging.AddSystemdConsole();

builder.Services.AddHangfire(/* [...] */);
builder.Services.AddHangfireServer();

/* [...] */

builder.Build().Run();

Next, register your background service(s) using builder.Services.AddHostedService<YourConcreteBackgroundServiceType>(). The background service type must implement IHostedService or be a subclass of BackgroundService. You can learn more about background services here.

Keep in mind that if you host long-running services in your worker, you should consider enabling server garbage collection for performance implications. You can learn more about this topic on this documentation page about server garbage collection.

<PropertyGroup>
  <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

Let’s wrap this up with a final tip. If you want to create a short-lived console application that leverages the full potential of minimal workers and .NET extensions libraries, you can create a BackgroundService that calls IHostApplicationLifetime.StopApplication() when the work is done:

internal sealed class MyShortLivedService : BackgroundService
{
    private readonly IHostApplicationLifetime _applicationLifetime;

    public MyShortLivedService(IHostApplicationLifetime applicationLifetime)
    {
        this._applicationLifetime = applicationLifetime;
    }

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // [...]
        // Do some work then shut down the application
        this._applicationLifetime.StopApplication();
        return Task.CompletedTask;
    }
}

References