Generic Host
Console apps or ASP.NET Core background tasks can use IHostedService
to run.
Such apps are also perfect to run in:
- containers
- system services (e.g. via systemd)
IHostedService
StartAsync
The StartAsync
method of the IHostedService
interface is async, but it’s run
inline during startup. If the hosted service is a long-running one, it should
return a Task
immediately and schedule the work on a separate thread. Using
await
is also not recommended.
BackgroundService
The BackgroundService
is a convenient base class. It’s designed to be used
with long-running tasks. It has just one method to override - ExecuteAsync()
.
We can freely use await
and never-ending loops (although we should be checking
CancellationToken
).
HttpClient and other non-singleton services
If our IHostedService
implementation is long-lived we shouldn’t inject HTTP
services there (e.g. typed HttpClients). HttpClient
should be short-lived. We
could either use IServiceProvider
to get new typed client in some interval
(service locator - not ideal) or use IHttpClientFactory
.
Terminating the app
The hosted service can terminate the app when finished the execution. An
instance of IHostApplicationLifetime
needs to be injected and used:
_logger?.LogInformation("Terminating Application");_applicationLifetime.StopApplication();
Worker Service
Worker Service is a console app without the ASP.NET Core stuff.
We can easily create a Worker Service project with dotnet new worker
. It
creates a simple Worker
class that inherits from BackgroundService
and
Program.cs
that sets up IHost
.
To get access to IHttpCLientFactory
we need to add the
Microsoft.Extensions.Http
NuGet package.
Systemd
To turn an app into a systemd service, we’d install the
Microsoft.Extensions.Hosting.Systemd
package. Then we’d call the
UseSystemd()
method on IHostBuilder
.
Quartz.NET
Quartz.NET is a scheduler library that enable some more advanced features of background services, like:
- CRON
- running multiple instances of it, with options to control concurrency (similar to RedLock)
It acts as a
Manual Creation
We can also create the boilerplate manually.
Imports
using Microsoft.Extensions.Configuration;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Hosting;using System;using System.Threading.Tasks;
Creating an IHostBuilder
Simple:
private static IHostBuilder GetHostBuilder(string[] args){ return Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { services.AddSingleton(hostContext.Configuration.GetSection("AppConfiguration").Get<AppConfiguration>()); services.AddHostedService<InfluxCompleteSetupService>(); });}
With configuration:
private static IHostBuilder GetHostBuilder(){ var builder = new HostBuilder() .ConfigureAppConfiguration((hostBuilderContext, configBuilder) => { configBuilder.AddJsonFile("appsettings.json", optional: true); configBuilder.AddEnvironmentVariables(); }) .ConfigureServices((hostBuilderContext, services) => { services.AddAllServices(hostBuilderContext.Configuration.GetSection("AppConfiguration").Get<AppConfiguration>()); }) .ConfigureLogging((hostBuilderContext, loggingBuilder) => { loggingBuilder.AddConfiguration(hostBuilderContext.Configuration.GetSection("Logging")); loggingBuilder.AddConsole(); });
return builder;}
Running the application
static async Task Main(string[] args){ try { var builder = GetHostBuilder(args); await builder.RunConsoleAsync(options => options.SuppressStatusMessages = true); } catch (Exception e) { Console.WriteLine("Program run into an exception"); Console.WriteLine(e.Message); Console.WriteLine("Press any key to exit"); Console.ReadKey(); }}
.NET 6:
using Microsoft.Extensions.Hosting;using Microsoft.Extensions.DependencyInjection;
try { using var host = Host.CreateDefaultBuilder(args) .ConfigureServices((_, services) => services .AddSingleton<InputReader>() .AddSingleton<BmiCalculator>() .AddHostedService<App>()) .Build();
await host.RunAsync();}catch (Exception e){ Console.WriteLine("Program run into an exception"); Console.WriteLine(e.Message); Console.WriteLine("Press any key to exit"); Console.ReadKey();}