Modern minimal workers in .NET
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
(likeappsettings.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:
HostApplicationBuilder.Services
to register services,HostApplicationBuilder.Configuration
to add more configuration providers,HostApplicationBuilder.Logging
to modify the logging configuration and register more logging providers,HostApplicationBuilder.Environment
to have access to the current environment.
Here’s an example where we use all of these properties:
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
Subscribe to my newsletter
Read articles from Anthony Simmon directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by