When a user calls an API endpoint to "Process 3 Months of Payroll", you cannot make them wait 5 minutes for the HTTP request to finish. A browser will time out. You must instantly return an HTTP 202 Accepted status, and process the heavy computational logic in the background. ASP.NET Core handles this using IHostedService.
[HttpPost("process-payroll")]
public IActionResult ProcessPayroll()
{
// NEVER DO THIS.
Task.Run(() => {
_payrollService.ExecuteHeavyMath(); // Takes 5 minutes
});
return Accepted();
}
If the ASP.NET Core server restarts or the App Pool recycles (which happens daily in IIS), the background Task is instantly murdered by the CPU. The payroll stops halfway. Employees aren't paid. Data gets corrupted.
Inheriting from BackgroundService allows you to create a long-running daemon that runs as a singleton deep inside the ASP.NET Core engine. It is physically aware of the server's lifecycle and shuts down gracefully.
public class PayrollWorker : BackgroundService
{
private readonly ILogger<PayrollWorker> _logger;
public PayrollWorker(ILogger<PayrollWorker> logger)
{
_logger = logger;
}
// This method executes immediately when the API server boots up
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Payroll Background Worker Started.");
// Loop continuously UNTIL the server administrator shuts down the API
while (!stoppingToken.IsCancellationRequested)
{
// Example: Wake up every 24 hours to check for pending payrolls
await Task.Delay(TimeSpan.FromHours(24), stoppingToken);
_logger.LogInformation("Processing Payroll...");
// Execute heavy logic safely
}
}
}
var builder = WebApplication.CreateBuilder(args);
// Registers the class to run alongside the HTTP server
builder.Services.AddHostedService<PayrollWorker>();
var app = builder.Build();
A BackgroundService is instantiated as a Singleton. Therefore, you cannot inject a Scoped service like EF Core DbContext directly into its constructor. You must dynamically yield a Scope at runtime.
public class DBWorker : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
public DBWorker(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider; // Inject the Factory
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Create a dedicated memory scope
using (var scope = _serviceProvider.CreateScope())
{
// Safely extract the Scoped DbContext!
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var users = await dbContext.Users.ToListAsync(stoppingToken);
// Process users...
}
}
}
Q: "If an enterprise API needs to execute a critical background task exactly every Tuesday at 3:00 AM, is IHostedService the correct tool for the job?"
Architect Answer: "No. `IHostedService` is a persistent thread runner, but it completely lacks Crontab scheduling logic. If you try to manually calculate `Task.Delay` to wait until Tuesday, you introduce massive drift bugs and reboot anomalies. Furthermore, if you scale your API to 3 instances behind a load balancer, ALL 3 instances will run the `IHostedService` simultaneously, processing the payroll 3 times! For precise scheduling and distributed job locking across multiple servers, you must integrate an external tool like Hangfire, Quartz.NET, or completely move the workload out of the API into an Azure Function triggered by a Timer."