What you want is already available through the Worker Service template. This template creates console applications that can then be hosted as either Windows or Linux services.
Worker services are described in Steve Gordon's What are .NET Worker Services?. Systemd hosting is explained in in Scott Hanselman's dotnet new worker - Windows Services or Linux systemd services in .NET Core. These two articles are a lot cleaner and easier to understand than the official docs. Start from these two before reading up on BackgroundService.
Worker class
A worker service is a class that implements the IHostedService
interface and lives as long as the Host
of an application lives. Typically, this is done through the BackgroundService
base class which implements the StartAsync
and StopAsync
methods so a worker only needs to implement ExecuteAsync
:
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
await Task.Delay(1000, stoppingToken);
}
}
}
When termination is signaled either through the console or a system message/signal, the Host notifies the service to exit gracefully before it's forcefully shut down.
public static async Task Main(string[] args)
{
await CreateHostBuilder(args).Build().RunAsync();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHostedService<Worker>();
});
This host is the same one used by ASP.NET Core services, which means one can configure dependency injection, logging, configuration etc
Console Lifecycle
RunConsoleAsync can be used instead of RunAsync()
to allow the application to shutdown on Ctrl+C (on Windows) or SIGINT/SIGTERM (on Linux)
Hosting
The Microsoft.Extensions.Hosting.WindowsService and Microsoft.Extensions.Hosting.Systemd packages take care of setting up the console application to run as either a Windows or systemd service.
To either hosting method, all that's needed is a call to the proper function:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices(services =>
{
services.AddHostedService<Worker>();
});
or
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSystemd()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});