Hangfire In .NET Core 6 - Background Jobs

What is Hangfire?

Hangfire is an open-source and well-documented task scheduler for ASP.NET and ASP.NET Core and is completely free for commercial use. It's multi-threaded, easily scalable, and offers a variety of job types. It's well-structured, simple to use, and gives a powerful performance.

Why is Hangfire used?

It is mainly used to perform background tasks such as batch/email notification, batch import of files, video/image processing, database maintaining, file purging, etc. Hangfire can be used for background tasks with high/low CPU consumption, short/long running tasks, recurring jobs, fire and forget, and many more.

Hangfire Dashboard and Database 

Hangfire Dashboard, we can see all the running and scheduled jobs we create with our Hangfire client. We can also monitor servers, job retries, and failed jobs and monitor all jobs in the queue. Another great thing we can do in the Dashboard is manually trigger any existing jobs.

For storing job definitions and statuses, Hangfire can use a SQL Server database by default, and we can also choose other options.

Different Types of Jobs in Hangfire

Different types of jobs that are available in Hangfire.

  • Fire-and-Forget Jobs
    Fire-and-forget jobs are executed only once and almost immediately after creation. 
  • Delayed Jobs
    Delayed jobs are executed only once, but not immediately, after a specific time interval.
  • Recurring Jobs
    Recurring jobs fire many times on the specified CRON schedule.  
  • Continuations
    Continuations are executed when its parent job has been finished. 

There are also two jobs available in Hangfire, but these are present in the Hangfire Pro version and used to execute multiple jobs in batch as a single entity, which are below. 

  • Batches
    A batch is a group of background jobs created atomically and considered a single entity. 
  • Batch Continuations
    Batch continuation is fired when all background jobs in a parent batch are finished. 

Set up Hangfire in ASP.NET Core

Step 1

Let's create a new ASP.NET Core Web API project in Visual Studio 2022

Hangfire in .NET Core 6

Step 2

Provide the Project name HangfireDemo and then provide the location

Hangfire in .NET Core 6

Step 3

Provide some additional information like .NET 6, Configure for HTTPS, and enable Open API support

Hangfire in .NET Core 6

Step 4

Install the below NuGet packages :

  • Hangfire.AspNetCore
  • Hangfire.SqlServer

Hangfire in .NET Core 6

Step 5

Create a new API Controller named as HomeController

using Hangfire;
using Microsoft.AspNetCore.Mvc;

namespace HangfireDemo.Controllers
{
    [ApiController]
    public class HomeController : Controller
    {
        [HttpGet]
        [Route("FireAndForgetJob")]
        public string FireAndForgetJob()
        {
            //Fire - and - Forget Jobs
            //Fire - and - forget jobs are executed only once and almost immediately after creation. 
            var jobId = BackgroundJob.Enqueue(() => Console.WriteLine("Welcome user in Fire and Forget Job Demo!"));

            return $"Job ID: {jobId}. Welcome user in Fire and Forget Job Demo!";
        }

        [HttpGet]
        [Route("DelayedJob")]
        public string DelayedJob()
        {
            //Delayed Jobs
            //Delayed jobs are executed only once too, but not immediately, after a certain time interval.
            var jobId = BackgroundJob.Schedule(() => Console.WriteLine("Welcome user in Delayed Job Demo!"), TimeSpan.FromSeconds(60));

            return $"Job ID: {jobId}. Welcome user in Delayed Job Demo!";
        }

        [HttpGet]
        [Route("ContinuousJob")]
        public string ContinuousJob()
        {
            //Fire - and - Forget Jobs
            //Fire - and - forget jobs are executed only once and almost immediately after creation.
            var parentjobId = BackgroundJob.Enqueue(() => Console.WriteLine("Welcome user in Fire and Forget Job Demo!"));

            //Continuations
            //Continuations are executed when its parent job has been finished.
            BackgroundJob.ContinueJobWith(parentjobId, () => Console.WriteLine("Welcome Sachchi in Continuos Job Demo!"));

            return "Welcome user in Continuos Job Demo!";
        }

        [HttpGet]
        [Route("RecurringJob")]
        public string RecurringJobs()
        {
            //Recurring Jobs
            //Recurring jobs fire many times on the specified CRON schedule. 
            RecurringJob.AddOrUpdate(() => Console.WriteLine("Welcome user in Recurring Job Demo!"), Cron.Hourly);

            return "Welcome user in Recurring Job Demo!";
        }

        [HttpGet]
        [Route("BatchesJob")]
        public string BatchesJob()
        {
            //Batches - This option is available into hangfire Pro only
            //Batch is a group of background jobs that is created atomically and considered as a single entity.
            //Commenting the code as it's only available into Pro version


            //var batchId = BatchJob.StartNew(x =>
            //{
            //    x.Enqueue(() => Console.WriteLine("Batch Job 1"));
            //    x.Enqueue(() => Console.WriteLine("Batch Job 2"));
            //});

            return "Welcome user in Batches Job Demo!";
        }

        [HttpGet]
        [Route("BatchContinuationsJob")]
        public string BatchContinuationsJob()
        {
            //Batch Continuations - This option is available into hangfire Pro only
            //Batch continuation is fired when all background jobs in a parent batch finished. 
            //Commenting the code as it's only available into Pro version

            //var batchId = BatchJob.StartNew(x =>
            //{
            //    x.Enqueue(() => Console.WriteLine("Batch Job 1"));
            //    x.Enqueue(() => Console.WriteLine("Batch Job 2"));
            //});

            //BatchJob.ContinueBatchWith(batchId, x =>
            //{
            //    x.Enqueue(() => Console.WriteLine("Last Job"));
            //});

            return "Welcome user in Batch Continuations Job Demo!";
        }
    }
}

Step 6

Configure things in Program.cs related to Hangfire, like SQL Server Database Connection and middleware

using Hangfire;
using Hangfire.SqlServer;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddHangfire(configuration => configuration
       .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
       .UseSimpleAssemblyNameTypeSerializer()
       .UseRecommendedSerializerSettings()
       .UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection"), new SqlServerStorageOptions
       {
           CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
           SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
           QueuePollInterval = TimeSpan.Zero,
           UseRecommendedIsolationLevel = true,
           DisableGlobalLocks = true
       }));
builder.Services.AddHangfireServer();

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseHangfireDashboard("/dashboard");

app.UseAuthorization();

app.MapControllers();

app.Run();

Step 7

Configure the connection string into the appsettings.json file.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Hangfire": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "HangfireConnection": "Server=DESKTOP-V06VDPI\\SQLEXPRESS;Database=Hangfire;User Id=sa;Password=sa@123;Trusted_Connection=True;Integrated Security=SSPI;"
  }
}

Step 8

Now, run the application. It will open up the swagger window and show all API Endpoints we have created. We will hit the API one by one to test the things.

Hangfire in .NET Core 6

Also, it will create the tables in Hangfire Database related to managing the Hangfire Jobs.

Hangfire in .NET Core 6

Step 9

After hitting the API, you can now open the Hangfire dashboard to manage background running jobs.

Open up a new window/tab and hit the URI https://localhost:7095/dashboard

We have configured /Dashboard URI into Program.cs file, so our route is /Dashboard

If you haven't configured any route name into Program.cs file, then your Dashboard URI will be the default URI https://localhost:7095/hangfire

Now hit the API one by one and monitor the Dashboard.

Hangfire in .NET Core 6

Hangfire in .NET Core 6

Hangfire in .NET Core 6

The Dashboard shows all the running and scheduled jobs we created with our Hangfire client. We can also monitor servers, job retries, and failed jobs and monitor all jobs in the queue. Another great thing we can do in the Dashboard is manually trigger existing jobs.

Call Jobs through Hangfire Interface via Dependency Injection

In our API's HomeController, we have used hangfire's classes to call different Jobs like BackgroundJob and RecurringJob.

We can also use Hangfire's Interface to call different Jobs like IBackgroundJobClient and IRecurringJobManager.

Sample Code for calling different  Jobs through Interface via Dependency Injection.

using Hangfire;
using Microsoft.AspNetCore.Mvc;

namespace HangfireDemo.Controllers
{
    public class HangfireController : Controller
    {
        private readonly IBackgroundJobClient _backgroundJobClient;
        private readonly IRecurringJobManager _recurringJobManager;
        public HangfireController(IBackgroundJobClient backgroundJobClient, IRecurringJobManager recurringJobManager)
        {
            _backgroundJobClient = backgroundJobClient;
            _recurringJobManager = recurringJobManager;
        }

        [HttpGet]
        [Route("IFireAndForgetJob")]
        public string FireAndForgetJob()
        {
            //Fire - and - Forget Jobs
            //Fire - and - forget jobs are executed only once and almost immediately after creation. 
            var jobId = _backgroundJobClient.Enqueue(() => Console.WriteLine("Welcome user in Fire and Forget Job Demo!"));

            return $"Job ID: {jobId}. Welcome user in Fire and Forget Job Demo!";
        }

        [HttpGet]
        [Route("IDelayedJob")]
        public string DelayedJob()
        {
            //Delayed Jobs
            //Delayed jobs are executed only once too, but not immediately, after a certain time interval.
            var jobId = _backgroundJobClient.Schedule(() => Console.WriteLine("Welcome user in Delayed Job Demo!"), TimeSpan.FromSeconds(60));

            return $"Job ID: {jobId}. Welcome user in Delayed Job Demo!";
        }

        [HttpGet]
        [Route("IContinuousJob")]
        public string ContinuousJob()
        {
            //Fire - and - Forget Jobs
            //Fire - and - forget jobs are executed only once and almost immediately after creation.
            var parentjobId = _backgroundJobClient.Enqueue(() => Console.WriteLine("Welcome user in Fire and Forget Job Demo!"));

            //Continuations
            //Continuations are executed when its parent job has been finished.
            BackgroundJob.ContinueJobWith(parentjobId, () => Console.WriteLine("Welcome Sachchi in Continuos Job Demo!"));

            return "Welcome user in Continuos Job Demo!";
        }

        [HttpGet]
        [Route("IRecurringJob")]
        public string RecurringJobs()
        {
            //Recurring Jobs
            //Recurring jobs fire many times on the specified CRON schedule. 
            _recurringJobManager.AddOrUpdate("jobId", () => Console.WriteLine("Welcome user in Recurring Job Demo!"), Cron.Hourly);

            return "Welcome user in Recurring Job Demo!";
        }
    }
}

Summary

In this article, we looked at Hangfire, which we have used in .NET 6 to schedule background jobs. Hoping you understand the things related to Hangfire.