1. Introduction
In modern web applications, not all tasks should run during the main user request. Some processes—like sending emails, generating reports, cleaning up logs, or syncing data—can take seconds or even minutes to complete. Running these tasks inline would slow down the application and create a poor user experience. This is where background jobs come into play.
A background job is any task that runs outside of the main request/response cycle. Instead of making users wait for time-consuming operations, the application delegates these tasks to run in the background. This improves performance, keeps the application responsive, and allows developers to handle recurring or long-running work reliably.
For example:
- Sending a confirmation email after user registration.
- Generating invoices or reports at scheduled intervals.
- Processing uploaded files (e.g., resizing images, parsing CSVs).
- Performing database cleanups or backups.
With the release of .NET 9, Microsoft continues to provide strong support for background processing. Developers can use built-in approaches like IHostedService or BackgroundService for simple scenarios. However, for more advanced requirements—such as retries, job persistence, and monitoring—frameworks like Hangfire offer a much more robust solution.
In this article, we’ll explore how to leverage Hangfire in .NET 9 to implement reliable, scalable, and maintainable background jobs.
2. What is Hangfire?
Hangfire is an open-source framework for managing background jobs in .NET applications. It allows developers to run fire-and-forget, delayed, recurring, or continuation jobs without writing complex infrastructure code. Hangfire handles job execution, persistence, retries, and monitoring out of the box, making it one of the most popular choices for background processing in the .NET ecosystem.
At its core, Hangfire uses a persistent storage (SQL Server, PostgreSQL, Redis, etc.) to queue and track jobs. This ensures that even if your application restarts or crashes, pending jobs will still be processed reliably.
Here’s why Hangfire often makes a better choice:
- Persistence: Jobs survive application restarts and crashes.
- Retries & Error Handling: Built-in retry logic prevents job loss.
- Monitoring: The Dashboard provides visibility into job execution.
- Scalability: Can run multiple servers to process jobs concurrently.
- Flexibility: Supports different storage providers (SQL, Redis, etc.).
3. Setting Up Hangfire in .NET 9
Now that we understand what Hangfire is and why it’s powerful, let’s walk through the setup in a .NET 9 Web API or MVC project. Currently I'm choosing to web api project to implement Hangfire background job.
3.1 Installing Required NuGet Packages
Add the below NuGet packages to the project.
Hangfire.AspNetCore
Hangfire.SqlServer
Hangfire.AspNetCore → Integrates Hangfire with ASP.NET Core apps
Hangfire.SqlServer → Enables SQL Server as persistent storage for jobs
If you’re using a different storage provider (e.g., PostgreSQL, Redis), install the respective package.
3.2 Configuring Hangfire in Program.cs
In .NET 9, most configurations go inside Program.cs
.
using Hangfire;
var builder = WebApplication.CreateBuilder(args);
// 1. Add Hangfire services and configure SQL Server storage
builder.Services.AddHangfire(config =>
config.UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection")));
// 2. Add Hangfire background processing server
builder.Services.AddHangfireServer();
var app = builder.Build();
// 3. Enable Hangfire Dashboard
app.UseHangfireDashboard("/hangfire");
// Example: register a sample job
app.MapGet("/", (IBackgroundJobClient backgroundJobs) =>
{
backgroundJobs.Enqueue(() => Console.WriteLine("Hello from Hangfire!"));
return "Background job has been queued.";
});
app.Run();
3.3 Connecting with SQL Server for Storage
In your appsettings.json, define the Hangfire connection string:
{
"ConnectionStrings": {
"HangfireConnection": "Server=localhost;Database=HangfireDB;User Id=sa;Password=YourPassword;TrustServerCertificate=True;"
}
}
4. Types of Background Jobs in Hangfire
One of Hangfire’s biggest strengths is the variety of job types it supports. Depending on your scenario, you can run jobs immediately, after a delay, on a schedule, or chained together. Let’s go through each type with examples.
4.1 Fire-and-Forget Jobs
These are executed immediately and only once. They are useful for tasks that don’t need to block the main user request.
using Hangfire;
public class EmailService
{
public void SendWelcomeEmail(string email)
{
Console.WriteLine($"Welcome email sent to {email}");
}
}
// Usage
BackgroundJob.Enqueue<EmailService>(service => service.SendWelcomeEmail("user@example.com"));
4.2 Delayed Jobs
These jobs are scheduled to run after a specified delay.
BackgroundJob.Schedule(() => Console.WriteLine("This job runs after 2 minutes"),
TimeSpan.FromMinutes(2));
4.3 Recurring Jobs (with Cron)
Recurring jobs run on a schedule, similar to CRON jobs in Linux.
RecurringJob.AddOrUpdate("daily-report",
() => Console.WriteLine("Generating daily report..."),
Cron.Daily);
Other Cron expressions provided by Hangfire:
Cron.Minutely
→ every minute
Cron.Hourly
→ every hour
Cron.Daily
→ once a day
Cron.Weekly
→ once a week
Cron.Monthly
→ once a month
4.4 Continuation Jobs
These jobs run only after a parent job has finished.
var parentJobId = BackgroundJob.Enqueue(() => Console.WriteLine("Step 1: First job executed"));
BackgroundJob.ContinueJobWith(parentJobId,
() => Console.WriteLine("Step 2: Continuation job executed"));
5. Exploring the Hangfire Dashboard
One of Hangfire’s most powerful features is its interactive dashboard. It provides real-time visibility into background jobs, queues, and processing servers. With just a browser, you can monitor the health of y
5.1 Navigating the UI
Once enabled in your app:
app.UseHangfireDashboard("/hangfire");
You can open http://localhost:5000/hangfire
in the browser.
The dashboard is divided into several sections:
- Jobs → View jobs by state (Succeeded, Failed, Scheduled, Processing, Deleted).
- Recurring Jobs → See all scheduled recurring tasks with their CRON expressions.
- *Queues *→ Monitor job queues and check how many workers are processing them.
- Servers → Displays active Hangfire servers running your jobs.
- Retries → Track jobs that have failed and are waiting for a retry.
5.2 Monitoring Running & Completed Jobs
From the Jobs tab, you can:
- Check which jobs are currently running.
- See detailed logs for completed jobs.
- Inspect job parameters and execution history.
5.3 Retrying Failed Jobs
If a job fails (due to a network error, database timeout, etc.), Hangfire automatically retries it a few times based on the retry policy.
- You can manually retry a failed job from the dashboard.
- Failed jobs are highlighted with detailed exception logs.
- This makes Hangfire extremely reliable for critical tasks like sending invoices or payments.
5.4 Securing the Dashboard with Authentication
By default, the dashboard is open to anyone who knows the URL. This is fine for local development but must be secured in production.
You can add authentication using DashboardOptions:
using Hangfire.Dashboard;
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] { new MyDashboardAuthorizationFilter() }
});
public class MyDashboardAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
// Example: allow only authenticated users
var httpContext = context.GetHttpContext();
return httpContext.User.Identity?.IsAuthenticated ?? false;
}
}
6. Advanced Usage Patterns
Once you’re comfortable with basic Hangfire jobs, you can unlock its advanced features to build more scalable and maintainable systems. Let’s explore some common patterns.
6.1 Using Dependency Injection in Hangfire Jobs
Hangfire integrates with ASP.NET Core’s built-in Dependency Injection (DI) container. This allows you to inject services directly into your job methods instead of writing static helpers.
public interface IEmailService
{
void SendWelcomeEmail(string email);
}
public class EmailService : IEmailService
{
public void SendWelcomeEmail(string email)
{
Console.WriteLine($"Email sent to {email}");
}
}
// Register service in Program.cs
builder.Services.AddScoped<IEmailService, EmailService>();
// Enqueue job with DI
BackgroundJob.Enqueue<IEmailService>(service => service.SendWelcomeEmail("user@example.com"));
6.2 Creating a Custom Job Scheduler with Cron
For advanced scheduling, you can define custom CRON expressions.
Example: Run a cleanup job every Monday at midnight.
RecurringJob.AddOrUpdate("weekly-cleanup",
() => Console.WriteLine("Performing weekly cleanup..."),
"0 0 * * 1"); // Cron format: minute hour day month weekday
6.3 Handling Concurrency & Queue Management
By default, all jobs run in the default queue. For more control, you can assign jobs to custom queues and configure workers accordingly.
// Enqueue job into a custom "emails" queue
BackgroundJob.Enqueue(() => Console.WriteLine("Email Job"), new EnqueuedState("emails"));
In Program.cs, configure Hangfire Server to listen to multiple queues:
builder.Services.AddHangfireServer(options =>
{
options.Queues = new[] { "default", "emails", "reports" };
});
6.4 Scaling Hangfire with Multiple Servers
Hangfire is designed for horizontal scaling. You can run multiple instances of your application (on different servers or containers), and they will all share the same job storage (e.g., SQL Server or Redis).
- Each server pulls jobs from the shared storage.
- Jobs are distributed across workers automatically.
- Failed jobs are retried by any available server.
This makes Hangfire suitable for high-traffic, production workloads.
builder.Services.AddHangfireServer(options =>
{
options.WorkerCount = Environment.ProcessorCount * 5; // Scale with CPU cores
});
7. Best Practices for Production
Running Hangfire in production requires careful planning to ensure reliability, security, and performance. Below are some best practices you should follow:
7.1. Logging & Monitoring Background Jobs
- Integrate structured logging (e.g., with Serilog or NLog) to track job execution.
- Use Hangfire’s built-in job history tracking for insights.
- Consider external monitoring tools like Application Insights, ELK stack, or Prometheus/Grafana for deeper visibility.
public class EmailService
{
private readonly ILogger<EmailService> _logger;
public EmailService(ILogger<EmailService> logger)
{
_logger = logger;
}
public void SendWelcomeEmail(string userEmail)
{
_logger.LogInformation("Sending welcome email to {User}", userEmail);
// email sending logic
}
}
7.2 Retry Policies & Error Handling
- Hangfire automatically retries failed jobs (default: 10 attempts).
- Configure retry policies based on job criticality.
- Use
[AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Fail)]
to fine-tune.
public class ReportGenerator
{
[AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Fail)]
public void GenerateDailyReport()
{
// Report generation logic
}
}
7.3. Securing Hangfire Dashboard in Production
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
Authorization = new[] { new HangfireCustomAuthorizationFilter() }
});
Custom filter:
public class HangfireCustomAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize(DashboardContext context)
{
var httpContext = context.GetHttpContext();
return httpContext.User.Identity?.IsAuthenticated == true &&
httpContext.User.IsInRole("Admin");
}
}
7.4. Database Performance Considerations
- Hangfire stores jobs in SQL Server; optimize DB for high insert/update throughput.
- Use separate DB schema or instance if you have high job volume.
- Clean up old job history with Hangfire’s built-in housekeeping jobs.
app.UseHangfireServer(new BackgroundJobServerOptions
{
Queues = new[] { "default" },
WorkerCount = Environment.ProcessorCount * 5 // Tune based on load
});
8. Conclusion
Background job processing is a critical part of building modern, scalable applications, and Hangfire makes it both simple and powerful in the .NET 9 ecosystem. With its persistence, retry mechanisms, flexible job types, and intuitive dashboard, Hangfire provides a production-ready framework for handling tasks that don’t belong in the main request pipeline.
While BackgroundService may be sufficient for lightweight or one-off background tasks, Hangfire shines when you need reliability, visibility, and scalability. By following best practices—such as securing the dashboard, tuning retry policies, and monitoring system performance—you can ensure your background jobs run smoothly in production environments.
As .NET 9 continues to improve performance and developer experience, pairing it with Hangfire allows you to build robust, fault-tolerant, and scalable background processing systems that enhance both your application’s performance and your users’ experience.
Top comments (0)