Background Tasks Made Easy With Hangfire And .Net 5

Introduction

 
In this article we will learn about the Hangfire - .Net Library to make our background tasks and jobs easier in ASP.NET 5.0. As we all know, its newly launched Framework officially released in November. Here I am sharing the link to install the SDK for .Net 5
 
Prerequisites
  1. What is Hangfire and why do I need background tasks.
  2. Setup and Configure Hangfire
  3. Secure the Hangfire Dashboard.
  4. Hangfire Retention Time.
  5. Persistence with SQL Database.  

What is Hangfire and why do we need to use this?

 
Hangfire is a .Net Library which helps to create background tasks and make jobs easier in .Net applications.  It supports all types of tasks like "fire and forget" and "recurring" and continous jobs as well. You can learn more about this here: Hangfire
 
Why do I need background tasks? 
 
Background tasks are important in cases where you need to perform an operation or to schedule a task for a particular time. With the background task, the process can continue running in the background where the user cannot wait for the step by step process.
 

Setup and Configure Hangfire 

 
Create and set up project template with .Net 5
 
 
In order to configure Hangfire, we need to install hangfire related packages. Below are the 4 packages that help in configuration and setup authentication and to store job-related information in SQL.
 
 
In this project, I have used Data insertion to Database using background tasks - Hangfire and the Code first approach.
 
Models
 
 
 
Employee.cs
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.ComponentModel.DataAnnotations;  
  4. using System.Linq;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace Hangfire.Model  
  8. {  
  9.     public class Employee  
  10.     {  
  11.         [Key]  
  12.         public int Id { getset; }  
  13.         public string EmployeeName { getset; }  
  14.         public string Designation { getset; }   
  15.     }  
  16. }  
 AppDbContext
 
 
EmployeeDbContext.cs 
  1. using Hangfire.Model;  
  2. using Microsoft.EntityFrameworkCore;  
  3. using System;  
  4. using System.Collections.Generic;  
  5. using System.Linq;  
  6. using System.Threading.Tasks;  
  7.   
  8. namespace Hangfire.AppDbContext  
  9. {  
  10.     public partial class EmployeeDbContext : DbContext  
  11.     {  
  12.         public EmployeeDbContext(DbContextOptions options) : base(options)  
  13.         {  
  14.   
  15.         }  
  16.         public DbSet<Employee> Employees { getset; }  
  17.     }  
  18. }  
appsettings.js
  1. "ConnectionStrings": {  
  2.     "myconn""server=N-20RJPF2CFK06\\SQLEXPRESS; database=Temp;Trusted_Connection=True;"  
  3.   },  
Configure the connection string and Hangfire and services injection in the startup file.
 
Startup.cs 
  1. using Hangfire.AppDbContext;  
  2. using Hangfire.Services;  
  3. using HangfireBasicAuthenticationFilter;  
  4. using Microsoft.AspNetCore.Builder;  
  5. using Microsoft.AspNetCore.Hosting;  
  6. using Microsoft.AspNetCore.HttpsPolicy;  
  7. using Microsoft.AspNetCore.Mvc;  
  8. using Microsoft.EntityFrameworkCore;  
  9. using Microsoft.Extensions.Configuration;  
  10. using Microsoft.Extensions.DependencyInjection;  
  11. using Microsoft.Extensions.Hosting;  
  12. using Microsoft.Extensions.Logging;  
  13. using Microsoft.OpenApi.Models;  
  14. using System;  
  15. using System.Collections.Generic;  
  16. using System.Linq;  
  17. using System.Threading.Tasks;  
  18.   
  19. namespace Hangfire  
  20. {  
  21.     public class Startup  
  22.     {  
  23.         private static IEmployeeService employeeService;  
  24.         private readonly Job jobscheduler = new Job(employeeService);  
  25.         public Startup(IConfiguration configuration)  
  26.         {  
  27.             Configuration = configuration;  
  28.         }  
  29.   
  30.         public IConfiguration Configuration { get; }  
  31.   
  32.         // This method gets called by the runtime. Use this method to add services to the container.  
  33.         public void ConfigureServices(IServiceCollection services)  
  34.         {  
  35.   
  36.             services.AddControllers();  
  37.             services.AddSwaggerGen(c =>  
  38.             {  
  39.                 c.SwaggerDoc("v1"new OpenApiInfo { Title = "Hangfire", Version = "v1" });  
  40.             });  
  41.  
  42.             #region Configure Connection String  
  43.             services.AddDbContext<EmployeeDbContext>(item => item.UseSqlServer(Configuration.GetConnectionString("myconn")));  
  44.             #endregion  
  45.  
  46.             #region Configure Hangfire  
  47.             services.AddHangfire(c => c.UseSqlServerStorage(Configuration.GetConnectionString("myconn")));  
  48.             GlobalConfiguration.Configuration.UseSqlServerStorage(Configuration.GetConnectionString("myconn")).WithJobExpirationTimeout(TimeSpan.FromDays(7));  
  49.             #endregion  
  50.  
  51.             #region Services Injection  
  52.             services.AddTransient<IEmployeeService, EmployeeService>();  
  53.             #endregion  
  54.         }  
  55.   
  56.         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.  
  57.         public void Configure(IApplicationBuilder app, IWebHostEnvironment env,IBackgroundJobClient backgroundJobClient, IRecurringJobManager recurringJobManager)  
  58.         {  
  59.             if (env.IsDevelopment())  
  60.             {  
  61.                 app.UseDeveloperExceptionPage();  
  62.                 app.UseSwagger();  
  63.                 app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json""Hangfire v1"));  
  64.             }  
  65.  
  66.             #region Configure Hangfire  
  67.             app.UseHangfireServer();  
  68.   
  69.             //Basic Authentication added to access the Hangfire Dashboard  
  70.             app.UseHangfireDashboard("/hangfire"new DashboardOptions()  
  71.             {  
  72.                 AppPath = null,  
  73.                 DashboardTitle = "Hangfire Dashboard",  
  74.                 Authorization = new[]{  
  75.                 new HangfireCustomBasicAuthenticationFilter{  
  76.                     User = Configuration.GetSection("HangfireCredentials:UserName").Value,  
  77.                     Pass = Configuration.GetSection("HangfireCredentials:Password").Value  
  78.                 }  
  79.             },  
  80.                 //Authorization = new[] { new DashboardNoAuthorizationFilter() },  
  81.                 //IgnoreAntiforgeryToken = true  
  82.             }); ;  
  83.             #endregion  
  84.   
  85.             app.UseHttpsRedirection();  
  86.   
  87.             app.UseRouting();  
  88.   
  89.             app.UseAuthorization();  
  90.   
  91.             app.UseEndpoints(endpoints =>  
  92.             {  
  93.                 endpoints.MapControllers();  
  94.             });  
  95.  
  96.             #region Job Scheduling Tasks  
  97.             //recurringJobManager.AddOrUpdate("Insert Employee : Runs Every 1 Min", () => jobscheduler.JobAsync(), "*/1 * * * *");  
  98.             #endregion  
  99.         }  
  100.     }  
  101. }  
Then we have to create the table using the below migration commands in the package manager console.
 
Creates migration folder and migration script inside the target project.
  1. PM>  Add-Migration 'MigrationName'  
 
 
The next command  executes the migration script and creates a table in the database
  1. PM>  Update-Database  
Services
 
 
 
EmployeeService.cs
  1. using Hangfire.AppDbContext;  
  2. using Hangfire.Model;  
  3. using System;  
  4. using System.Collections.Generic;  
  5. using System.Linq;  
  6. using System.Threading.Tasks;  
  7.   
  8. namespace Hangfire.Services  
  9. {  
  10.     public class EmployeeService : IEmployeeService  
  11.     {  
  12.         #region Property  
  13.         private readonly EmployeeDbContext _employeeDbContext;  
  14.         #endregion  
  15.  
  16.         #region Constructor  
  17.         public EmployeeService(EmployeeDbContext employeeDbContext)  
  18.         {  
  19.             _employeeDbContext = employeeDbContext;  
  20.         }  
  21.         #endregion  
  22.  
  23.         #region Insert Employee  
  24.         public async Task<bool> InsertEmployeeAsync()  
  25.         {  
  26.             try  
  27.             {  
  28.                 Employee employee = new Employee()  
  29.                 {  
  30.                     EmployeeName = "Jk",  
  31.                     Designation = "Full Stack Developer"  
  32.                 };  
  33.                 await _employeeDbContext.AddAsync(employee);  
  34.                 await _employeeDbContext.SaveChangesAsync();  
  35.                 return true;  
  36.             }  
  37.             catch (Exception ex)  
  38.             {  
  39.                 throw;  
  40.             }  
  41.         }  
  42.         #endregion  
  43.     }  
  44. }  
IEmployeeService.cs
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Threading.Tasks;  
  5.   
  6. namespace Hangfire.Services  
  7. {  
  8.    public interface IEmployeeService  
  9.     {  
  10.         Task<bool> InsertEmployeeAsync();  
  11.     }  
  12. }  
The Service Injection is already done in the Startup. cs file
  1. services.AddTransient<IEmployeeService, EmployeeService>();  

Secure the Hangfire Dashboard

 
To Secure the hangfire dashboard we setup login authentication in order to access the hangfire dashboard.  I have hardcoded the username and password in the appsettings.js file to consume those in the startup.cs
 
appsettings.js
  1. "HangfireCredentials": {  
  2.     "UserName""admin",  
  3.     "Password""admin@123"  
  4.   }  
 Starup.cs
  1. //Basic Authentication added to access the Hangfire Dashboard  
  2.             app.UseHangfireDashboard("/hangfire"new DashboardOptions()  
  3.             {  
  4.                 AppPath = null,  
  5.                 DashboardTitle = "Hangfire Dashboard",  
  6.                 Authorization = new[]{  
  7.                 new HangfireCustomBasicAuthenticationFilter{  
  8.                     User = Configuration.GetSection("HangfireCredentials:UserName").Value,  
  9.                     Pass = Configuration.GetSection("HangfireCredentials:Password").Value  
  10.                 }  
  11.             },  
  12.             }); ;  

Hangfire Retention Time

 
Usually, the hangfire jobs running in the background will elapse for 24 hours. To avoid this I have to enable the basic setting to last this job in the dashboard for at least 1 week.
 
Startup.cs 
  1. GlobalConfiguration.Configuration.UseSqlServerStorage(Configuration.GetConnectionString("myconn")).WithJobExpirationTimeout(TimeSpan.FromDays(7));  

Persistence with SQL Database

 
Hangfire has an option to store all the job-related information in the database. For this we don't need anything we have to configure this setup in the Startup.cs and it automatically creates all the tables where we can see the job status and respective information in those tables.
 
Startup.cs
  1. services.AddHangfire(c => c.UseSqlServerStorage(Configuration.GetConnectionString("myconn")));  
 
 
The above set of tables were created automatically when we configured the setup and point to the database.
 
Create background tasks 
 
Job.cs
  1. using Hangfire.Services;  
  2. using System;  
  3. using System.Collections.Generic;  
  4. using System.Linq;  
  5. using System.Threading.Tasks;  
  6.   
  7. namespace Hangfire  
  8. {  
  9.     public class Job  
  10.     {  
  11.         #region Property  
  12.         private readonly IEmployeeService _employeeService;  
  13.         #endregion  
  14.  
  15.         #region Constructor  
  16.         public Job(IEmployeeService employeeService)  
  17.         {  
  18.             _employeeService = employeeService;   
  19.         }  
  20.         #endregion  
  21.  
  22.         #region Job Scheduler  
  23.         public async Task<bool> JobAsync()  
  24.         {  
  25.             var result = await _employeeService.InsertEmployeeAsync();  
  26.             return true;  
  27.         }  
  28.         #endregion  
  29.     }  
  30. }  
There are 4 types of jobs that mostly we will use.  I have created all 4 jobs in a startup.cs file.
 
Starup.cs 
  1. #region Job Scheduling Tasks  
  2.             // Recurring Job for every 5 min  
  3.             recurringJobManager.AddOrUpdate("Insert Employee : Runs Every 1 Min", () => jobscheduler.JobAsync(), "*/5 * * * *");  
  4.   
  5.             //Fire and forget job   
  6.             var jobId =  backgroundJobClient.Enqueue(() => jobscheduler.JobAsync());  
  7.   
  8.             //Continous Job  
  9.             backgroundJobClient.ContinueJobWith(jobId, () => jobscheduler.JobAsync());  
  10.   
  11.             //Schedule Job / Delayed Job  
  12.   
  13.             backgroundJobClient.Schedule(() => jobscheduler.JobAsync(), TimeSpan.FromDays(5));  
  14.             #endregion  
 Recurring job - Every 5 minutes the background task runs and inserts data into database.
 
Fire and Forget - This job runs only once when we run the application.
 
Continuous Job - When we want to run jobs one after another at that time this will be useful so that it will execute one by one. 
 
Schedule Job - If you want to schedule a task to run at a particular time.
 
Run the application
 
By default, the swagger endpoint will open. Now type hangfire in the URL by removing the swagger. It will ask for a username and password as we had already set up the authentication mechanism .
 
 
 
 If you try to access without login.
 
 
 
If you click on jobs and succeed then we will see the job execution status and its time, and also we can see the historical graph in the dashboard as well.
 
  
 We can see our scheduled jobs and recurring jobs in the tab and menu and also we have an option to delete the particular job that you no longer want to see.
 
Download the Source Code - GitHub
 
I hope this article helps you in creating background tasks as easily as possible!
 
Keep learning !!!!!!!