.NET Generic Host
Overview
The .NET Generic Host (Microsoft.Extensions.Hosting) provides a standardized framework for building non-HTTP applications such as console workers, background services, and message processors. It wires together dependency injection, configuration, logging, and application lifetime management into a single composable model. The same patterns used in ASP.NET Core (IServiceCollection, IConfiguration, ILogger) apply identically in hosted services.
The host manages the application lifecycle: it starts all registered IHostedService instances, waits for a shutdown signal (Ctrl+C, SIGTERM, or programmatic stop), and then gracefully shuts down all services in reverse registration order.
Basic Host Setup
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; var builder = Host.CreateApplicationBuilder(args); // Register services builder.Services.AddSingleton<IMessageQueue, RabbitMqMessageQueue>(); builder.Services.AddHostedService<OrderProcessingWorker>(); using var host = builder.Build(); await host.RunAsync();
BackgroundService for Long-Running Work
BackgroundService is the base class for implementing hosted services that run a loop or continuous process.
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
public sealed class OrderProcessingWorker : BackgroundService
{
private readonly ILogger<OrderProcessingWorker> _logger;
private readonly IServiceScopeFactory _scopeFactory;
public OrderProcessingWorker(
ILogger<OrderProcessingWorker> logger,
IServiceScopeFactory scopeFactory)
{
_logger = logger;
_scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Order processing worker started");
while (!stoppingToken.IsCancellationRequested)
{
try
{
using IServiceScope scope = _scopeFactory.CreateScope();
var processor = scope.ServiceProvider.GetRequiredService<IOrderProcessor>();
int processed = await processor.ProcessPendingOrdersAsync(stoppingToken);
_logger.LogDebug("Processed {Count} orders", processed);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_logger.LogError(ex, "Error processing orders");
}
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
}
_logger.LogInformation("Order processing worker stopped");
}
}
IHostedService for Startup/Shutdown Tasks
Use IHostedService directly when you need explicit control over StartAsync and StopAsync.
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
public sealed class CacheWarmupService : IHostedService
{
private readonly ILogger<CacheWarmupService> _logger;
private readonly ICacheWarmer _cacheWarmer;
public CacheWarmupService(
ILogger<CacheWarmupService> logger,
ICacheWarmer cacheWarmer)
{
_logger = logger;
_cacheWarmer = cacheWarmer;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Warming up caches...");
await _cacheWarmer.WarmAsync(cancellationToken);
_logger.LogInformation("Cache warmup complete");
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Cache warmup service stopped");
return Task.CompletedTask;
}
}
Multiple Hosted Services
Register multiple hosted services that run concurrently.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
// Configuration
builder.Services.Configure<WorkerOptions>(
builder.Configuration.GetSection("Worker"));
// Application services
builder.Services.AddScoped<IOrderProcessor, OrderProcessor>();
builder.Services.AddScoped<INotificationProcessor, NotificationProcessor>();
builder.Services.AddSingleton<ICacheWarmer, ProductCacheWarmer>();
// Hosted services (start in registration order)
builder.Services.AddHostedService<CacheWarmupService>();
builder.Services.AddHostedService<OrderProcessingWorker>();
builder.Services.AddHostedService<NotificationWorker>();
using var host = builder.Build();
await host.RunAsync();
Configuration and Logging
The generic host automatically loads configuration from standard sources and configures logging.
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
var builder = Host.CreateApplicationBuilder(args);
// Configuration is loaded automatically from:
// appsettings.json
// appsettings.{Environment}.json
// Environment variables
// Command-line arguments
// Add custom configuration sources
builder.Configuration.AddJsonFile("worker-settings.json", optional: true);
// Configure logging
builder.Logging.AddConsole();
builder.Logging.AddJsonConsole(options =>
{
options.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff ";
});
builder.Logging.SetMinimumLevel(LogLevel.Information);
// Bind options
builder.Services.Configure<WorkerOptions>(
builder.Configuration.GetSection("Worker"));
using var host = builder.Build();
await host.RunAsync();
public sealed class WorkerOptions
{
public int PollingIntervalSeconds { get; set; } = 10;
public int MaxBatchSize { get; set; } = 100;
public int MaxRetries { get; set; } = 3;
}
{
"Worker": {
"PollingIntervalSeconds": 5,
"MaxBatchSize": 50,
"MaxRetries": 3
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
Graceful Shutdown with IHostApplicationLifetime
React to application lifecycle events for cleanup.
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
public sealed class GracefulShutdownService : IHostedService
{
private readonly IHostApplicationLifetime _lifetime;
private readonly ILogger<GracefulShutdownService> _logger;
public GracefulShutdownService(
IHostApplicationLifetime lifetime,
ILogger<GracefulShutdownService> logger)
{
_lifetime = lifetime;
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_lifetime.ApplicationStarted.Register(() =>
_logger.LogInformation("Application has started"));
_lifetime.ApplicationStopping.Register(() =>
_logger.LogInformation("Application is shutting down..."));
_lifetime.ApplicationStopped.Register(() =>
_logger.LogInformation("Application has stopped"));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
Systemd and Windows Service Integration
Run the same code as a Linux systemd service or Windows Service.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
// Detect and integrate with the host OS service manager
builder.Services.AddWindowsService(options =>
{
options.ServiceName = "OrderProcessor";
});
builder.Services.AddSystemd(); // For Linux systemd
builder.Services.AddHostedService<OrderProcessingWorker>();
using var host = builder.Build();
await host.RunAsync();
Timed Background Service Pattern
A reusable pattern for services that execute on a timer.
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
public abstract class TimedBackgroundService : BackgroundService
{
private readonly ILogger _logger;
private readonly TimeSpan _interval;
protected TimedBackgroundService(ILogger logger, TimeSpan interval)
{
_logger = logger;
_interval = interval;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using PeriodicTimer timer = new(_interval);
while (await timer.WaitForNextTickAsync(stoppingToken))
{
try
{
await DoWorkAsync(stoppingToken);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_logger.LogError(ex, "Error in {Service}", GetType().Name);
}
}
}
protected abstract Task DoWorkAsync(CancellationToken stoppingToken);
}
public sealed class MetricsCollector : TimedBackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
public MetricsCollector(
ILogger<MetricsCollector> logger,
IServiceScopeFactory scopeFactory)
: base(logger, TimeSpan.FromMinutes(1))
{
_scopeFactory = scopeFactory;
}
protected override async Task DoWorkAsync(CancellationToken stoppingToken)
{
using var scope = _scopeFactory.CreateScope();
var metrics = scope.ServiceProvider.GetRequiredService<IMetricsService>();
await metrics.CollectAndPublishAsync(stoppingToken);
}
}
Best Practices
- •Use
Host.CreateApplicationBuilder(args)over the olderHost.CreateDefaultBuilder(args).ConfigureServices(...)pattern for cleaner, more concise setup in .NET 8+. - •Inject
IServiceScopeFactoryinBackgroundServiceand create a new scope per iteration to resolve scoped services likeDbContextsafely without captive dependency issues. - •Use
PeriodicTimer(introduced in .NET 6) instead ofTask.Delayin timer-based background services because it does not drift over time and respects cancellation cleanly. - •Always catch and log exceptions inside
ExecuteAsyncloops; an unhandled exception terminates the hosted service and, depending on configuration, may crash the entire application. - •Keep
Program.csfocused on host configuration and registration; move service registration intoIServiceCollectionextension methods and keep business logic in separate classes. - •Configure
HostOptions.ShutdownTimeoutto a value that matches your longest cleanup operation (default is 30 seconds) to avoid forceful termination during graceful shutdown. - •Use
IHostApplicationLifetime.ApplicationStoppingto register cleanup callbacks for resources that are not managed by the DI container, such as external connections or file handles. - •Call
AddWindowsService()andAddSystemd()to integrate with OS service managers, allowing the same binary to run as a console app in development and a daemon in production. - •Register startup tasks as
IHostedService(notBackgroundService) when they need to complete before other services start, since hosted services start in registration order. - •Set the
DOTNET_ENVIRONMENT(orASPNETCORE_ENVIRONMENT) environment variable to control whichappsettings.{Environment}.jsonfile is loaded, usingDevelopment,Staging, orProduction.