Introduction
In modern ASP.NET Core applications, especially those involving microservices, background processing is a key requirement. Whether it’s sending emails, processing Kafka messages, running cron-like jobs, or handling long-running workflows, background services are essential.
With the introduction of the Generic Host and robust improvements in .NET 6, 7, and 8, ASP.NET Core now provides powerful patterns through,
- IHostedService
- BackgroundService
- Worker Service templates
This article dives deep into how to design, build, and optimize background services for production.
Worker Services vs Hosted Services: What’s the Difference?
Feature |
IHostedService |
BackgroundService |
Worker Service Template |
Interface-based |
Yes |
Yes (abstracts IHostedService) |
Yes (project template-based) |
Long-running support |
Needs manual implementation |
Provides an infinite loop (ExecuteAsync) |
Yes |
Best for |
Short-lived or event-based tasks |
Long-running background jobs |
Standalone background apps |
Setting Up a Worker Service Project
You can create a worker service using.
dotnet new worker -n EmailBackgroundService
This creates a ready-to-run worker template using the Generic Host.
The Program.cs in .NET 8+ is minimal.
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
services.AddHostedService<EmailSenderWorker>();
})
.Build();
await host.RunAsync();
Creating a Custom BackgroundService
Here’s a simple example of a background worker.
public class EmailSenderWorker : BackgroundService
{
private readonly ILogger<EmailSenderWorker> _logger;
public EmailSenderWorker(ILogger<EmailSenderWorker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Sending queued emails at: {time}", DateTimeOffset.Now);
await SendPendingEmails();
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
}
private Task SendPendingEmails()
{
// Implement actual email logic here
return Task.CompletedTask;
}
}
Advanced Hosted Services with IHostedService
When you need fine-grained control over start/stop behavior (e.g., for message queue consumers).
public class KafkaConsumerService : IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
// Connect to Kafka and begin consuming
return Task.Run(() => ConsumeMessages(cancellationToken), cancellationToken);
}
public Task StopAsync(CancellationToken cancellationToken)
{
// Cleanup, disconnect
return Task.CompletedTask;
}
private void ConsumeMessages(CancellationToken cancellationToken)
{
// Poll Kafka and process messages
}
}
Managing Multiple Background Services
You can register multiple services easily.
services.AddHostedService<EmailSenderWorker>();
services.AddHostedService<KafkaConsumerService>();
services.AddHostedService<DataSyncWorker>();
Each runs independently inside the host.
Handling Exceptions & Graceful Shutdown
To avoid crashing the entire app due to unhandled exceptions in workers.
try
{
await SomeBackgroundTask();
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred.");
}
For graceful shutdown, ensure your workers respond to the CancellationToken from ExecuteAsync.
Dependency Injection in Background Services
Services are injected normally.
public class DataSyncWorker : BackgroundService
{
private readonly IDataService _dataService;
public DataSyncWorker(IDataService dataService)
{
_dataService = dataService;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await _dataService.SyncExternalData();
}
}
Scheduling and Cron-like Jobs
For scheduled tasks, consider,
- Quartz.NET for CRON jobs.
- Hangfire for background jobs with retry support.
- Or a Timer-based service with Task.Delay.
Example using the Cronos package.
var cron = CronExpression.Parse("0 */5 * * * *");
var next = cron.GetNextOccurrence(DateTimeOffset.Now);
Logging, Metrics & Observability
Integrate with,
- Serilog / Seq for structured logging
- Prometheus for metrics
- OpenTelemetry for traces and spans
Production Tips
- Always handle exceptions gracefully
- Use scoped services inside BackgroundService via IServiceScopeFactory
- Monitor using health checks (IHealthCheck)
- Separate retry logic (e.g., use Polly)
- Set max concurrency (e.g., in queues) to prevent overload
Conclusion
ASP.NET Core’s Hosted Services and Worker Services provide powerful primitives for executing background tasks in a clean, reliable, and scalable way.
Whether you're building message consumers, email dispatchers, or data processors, background services allow you to decouple, scale, and manage your application’s background logic like a pro.
Happy coding!