5 Steps to Run Schedule Jobs Using Hangfire in .NET Core – Windows ASP.NET Core Hosting 2024 | Review and Comparison

There are many steps to run schedule task in your application development. For example, we can delegate long running tasks to background threads. Sometimes, we also want to schedule background jobs to keep domain models in consistent states, especially when the model state is mutated due to a time-triggered event.

This post is to summarize the integration of Hangfire and .NET Core.

1. Install Hangfire NuGet Package

The stable version for Hangfire is 1.7. You can download this latest version.

Once we have created .NET Core web project, we can install the Hangfire NuGet package through Visual Studio NuGet Package Manager, or install the package via command line tools

dotnet add package Hangfire

The Hangfire NuGet package includes Hangfire.Core and Hangire.SqlServer. If you are using another Database Provider, then you need to install the corresponding NuGet package. Here, I use SqlServer for my example.

Once the NuGet package is installed, we will have a copy of SQL migration script located at C:\Users\{windows user name}\.nuget\packages\hangfire.sqlserver\1.6.22\tools folder. The SQL script is well written and designed, so that when we update our Hangfire version, we can confidently run the new script included in the package to update the SQL database schema.

Please run the SQL script before you proceed. If you are using a newer version of Hangfire, then run the SQL script in your version.

2. Define and Implement a Background Job

For example we want to run a job called MyJob. We can define an interface and implement the interface with a class where we could inject dependencies and lay out the logic flow of the background job. You can see the code below:

The interface exposes one public method, Task RunAtTimeOf(DateTime now), which can be used in unit tests or can be reused when the interface is injected to other places. The Task Run(IJobCancellationToken token) method will be used in Hangfire job scheduler (we will come back to this point in the next section).

3. Arrange Job Scheduler for Recurring Jobs

After we have implemented some background jobs, we then want to schedule recurring jobs in a centralized class. The code snippet below shows the skeleton of a typical job scheduler. For each recurring job, we add (or update) it to the ReccuringJob collection in Hangfire, which creates an entry in persistent storage. The recurring job registration also allows us to specify when or how frequently the job needs to be run. We can use CRON expressions to handle more complex scheduling.

The actual method being called is Run(JobCancellationToken.Null) in the job class, which is the method we defined in the previous section.

You may wonder how does Hangfire resolve dependencies for these recurring jobs. Luckily, Hangfire provides an extension method (vide infra) to easily hook in the dependency injection system in .NET Core.

4. Add Hangfire into .NET Core Service Collection

We first want to register our background jobs in the .NET Core service container. For example, in the ConfigureServices(IServiceCollection services) method in Startup class, we register the following two job classes with lifestyle of scoped, so that we can call these jobs in other services or controllers using dependency injection.

services.AddScoped<IEmailSendingJob, EmailSendingJob>;
services.AddScoped<IMyJob, MyJob>;

On the other hand, when jobs run in Hangfire, Hangfire needs to know their dependencies as well. Hangfire internally uses AspNetCoreJobActivator to locate required services and to create scopes for registered jobs. In order to integrate Hangfire with the .NET Core dependency injection, Hangfire provides a services.AddHangfire() extension method, which has done all the heavy lifting, including registering logger factory and registering AspNetCoreJobActivator, and so on. This extension method also takes Hangfire configurations. See code below.

The configurations can be used to customize JobActivator, to configure LogProvider, and to configure Hangfire server storage. For example, the code snippet above tells Hangfire the connection string for SQL server, the job queue polling interval, etc. You can research other options to suit your needs.

Once dependency injection is set up, we can configure the application to use Hangfire server when the application starts, so that all background jobs and recurring jobs can start to spin up. In the code snippet below, we add several lines of code in Configure(IApplicationBuilder app, IHostingEnvironment env) method in Startup class. We instruct the application to use Hangfire server, add a global filter and save/update the recurring jobs in Hangfire storage if needed.

These lines of code will only execute once when our application starts. The order of configuration pipeline is important. Please note the code which are commented out and make sure that the Hangfire configurations are in the right order.

5. Set up Hangfire Dashboard

Hangfire dashboard is a fantastic tool to glance through the statuses of background jobs. To set up the dashboard, it doesn’t need much work.

You might have noticed that we have added an extension method app.UseHangfireDashboard() in the code snippet in the above section. This extension method takes a parameter of dashboard path and a parameter of dashboard options.

Even though all our jobs are idempotent, we still don’t want somebody to accidentally trigger/remove a job, or we don’t want a random person to see the job statuses. Thus, protecting the dashboard is always necessary. We can secure the dashboard via HangfireDashboardAuthorizationFilter, which can be implemented as below.

With this kind of setup, we can navigate to the Hangfire dashboard via URL of “{host-name}/hangfire” and only users in a certain role have access to the dashboard. Of course, you can customize the authorization process to your requirements.


That’s all, that’s only simple example to setup a basic application with Hangfire server running and recurring jobs will execute at desired time.