In This article we will be discussing how we can implement background jobs in .Net with Hangfire library
You can watch the full video on youtube:
If you would like to have full access to the source code please consider supporting me on Patreon
https://www.patreon.com/mohamadlawand
Will start by doing an over view of what are background jobs
Background Jobs
They are tasks that runs in the background without any user interventions which help the application to run smoothly.
Some of the examples of the background
- for long and lengthy operations like data processing .
- Batch import from APIs, files โฆ
- Synchronisation functionalities
- Reporting functionalities.
- Notification services.
Hangfire Overview
- Hangfire is open-source and used to schedule background jobs at a particular event and time.
- Hangfire will do background processing without user intervention.
- Hangfire provides a dashboard which helps manage everything
Hangfire job types
Hangfire support different job types which are listed below
Fire-and-Forget Job: are executed only one time after certain conditions, we donโt care about the outcome of the job.
Delayed Job: are executed only once but after a specific interval of time.
Recurring Job: are ****executed many times after specified condition and time interval
Continuations Job: are ****executed when its parent job is executed and finished.
Coding
Now lets create the app
dotnet new webapi -n "FireAPI"
Next we need to install the packages
dotnet add package Hangfire
dotnet add package Hangfire.Core
dotnet add package Hangfire.Storage.SQLite
dotnet add package Hangfire.Dashboard.Basic.Authentication
We need to add the connection string to our Sqlite inside the appsettings.json
"ConnectionStrings": {
"DefaultConnection": "app.db"
}
Now we need to configure our Hangfire configuration inside our Program.cs
builder.Services.AddHangfire(configuration => configuration
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSQLiteStorage(builder.Configuration.GetConnectionString("DefautConnection")));
// Add the processing server as IHostedService
builder.Services.AddHangfireServer();
app.UseHangfireDashboard();
app.MapHangfireDashboard();
Now let us add a Models folder and add our first Model Driver.cs
public class Driver
{
public Guid Id { get; set; }
public string Name { get; set; } = "";
public int DriverNumber { get; set; }
public int Status {get;set;}
}
Next we need to add a controller which will allow us to manage drivers, we will be utilising an in-memory database.
using FireApp.Models;
using Microsoft.AspNetCore.Mvc;
namespace FireApp.Controllers;
[ApiController]
[Route("[controller]")]
public class DriversController : ControllerBase
{
private static List<Driver> drivers = new List<Driver>();
private readonly ILogger<DriversController> _logger;
public DriversController(ILogger<DriversController> logger)
{
_logger = logger;
}
[HttpGet]
public IActionResult GetDrivers()
{
var items = drivers.Where(x => x.Status == 1).ToList();
return Ok(items);
}
[HttpPost]
public IActionResult CreateDriver(Driver data)
{
if(ModelState.IsValid)
{
drivers.Add(data);
return CreatedAtAction("GetDriver", new {data.Id}, data);
}
return new JsonResult("Something went wrong") {StatusCode = 500};
}
[HttpGet("{id}")]
public IActionResult GetDriver(Guid id)
{
var item = drivers.FirstOrDefault(x => x.Id == id);
if(item == null)
return NotFound();
return Ok(item);
}
[HttpPut("{id}")]
public IActionResult UpdateDriver(Guid id, Driver item)
{
if(id != item.Id)
return BadRequest();
var existItem = drivers.FirstOrDefault(x => x.Id == id);
if(existItem == null)
return NotFound();
existItem.Name = item.Name;
existItem.DriverNumber = item.DriverNumber;
return NoContent();
}
[HttpDelete("{id}")]
public IActionResult DeleteDriver(Guid id)
{
var existItem = drivers.FirstOrDefault(x => x.Id == id);
if(existItem == null)
return NotFound();
existItem.Status = 0;
return Ok(existItem);
}
}
Now let us create a service which will be responsible to sending emails, updating database โฆ
In the root directory of the app we need to create a new folder called Services and add the following interface IServiceManagement
namespace FireApp.Services;
public interface IServiceManagement
{
void SendEmail();
void UpdateDatabase();
void GenerateMerchandise();
void SyncRecords();
}
Now let us build an implementation of this interface
namespace FireApp.Services;
public class ServiceManagement : IServiceManagement
{
public void GenerateMerchandise()
{
Console.WriteLine($"Generate Merchandise: long running service at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
}
public void SendEmail()
{
Console.WriteLine($"Send Email: delayed execution service at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
}
public void SyncRecords()
{
Console.WriteLine($"Sync Records: at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
}
public void UpdateDatabase()
{
Console.WriteLine($"Update Database: at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
}
}
We need to add the service implementation into the FireApp
builder.Services.AddScoped<IServiceManagement, ServiceManagement>();
Now let us add a long running service which we want to execute every 4h
RecurringJob.AddOrUpdate<IServiceManagement>(x => x.SyncRecords(), "0 * * ? * *");
For help with cron expression, check the following website: https://www.freeformatter.com/cron-expression-generator-quartz.html
Now let us update the controller to add the services
using FireApp.Models;
using FireApp.Services;
using Hangfire;
using Microsoft.AspNetCore.Mvc;
namespace FireApp.Controllers;
[ApiController]
[Route("[controller]")]
public class DriversController : ControllerBase
{
private static List<Driver> drivers = new List<Driver>();
private readonly ILogger<DriversController> _logger;
public DriversController(
ILogger<DriversController> logger)
{
_logger = logger;
}
[HttpGet]
public IActionResult GetDrivers()
{
var items = drivers.Where(x => x.Status == 1).ToList();
return Ok(items);
}
[HttpPost]
public IActionResult CreateDriver(Driver data)
{
if(ModelState.IsValid)
{
drivers.Add(data);
// Fire and Forget Job
var jobId = BackgroundJob.Enqueue<IServiceManagement>(x => x.SendEmail());
Console.WriteLine($"Job id: {jobId}");
return CreatedAtAction("GetDriver", new {data.Id}, data);
}
return new JsonResult("Something went wrong") {StatusCode = 500};
}
[HttpGet("{id}")]
public IActionResult GetDriver(Guid id)
{
var item = drivers.FirstOrDefault(x => x.Id == id);
if(item == null)
return NotFound();
return Ok(item);
}
[HttpPut("{id}")]
public IActionResult UpdateDriver(Guid id, Driver item)
{
if(id != item.Id)
return BadRequest();
var existItem = drivers.FirstOrDefault(x => x.Id == id);
if(existItem == null)
return NotFound();
existItem.Name = item.Name;
existItem.DriverNumber = item.DriverNumber;
var jobId = BackgroundJob.Schedule<IServiceManagement>(x => x.UpdateDatabase(), TimeSpan.FromSeconds(20));
Console.WriteLine($"Job id: {jobId}");
return NoContent();
}
[HttpDelete("{id}")]
public IActionResult DeleteDriver(Guid id)
{
var existItem = drivers.FirstOrDefault(x => x.Id == id);
if(existItem == null)
return NotFound();
existItem.Status = 0;
RecurringJob.AddOrUpdate<IServiceManagement>(x => x.SyncRecords(), Cron.Hourly);
return Ok(existItem);
}
}
Securing Hangfire
AppSetting.json we need to update it to the following
"HangfireCredentials": {
"UserName": "mohamad",
"Password": "Passw0rd"
}
And we need to update the program.cs to the following
app.UseHangfireDashboard("/hangfire", new DashboardOptions()
{
DashboardTitle = "Hangfire Dashboard",
Authorization = new[]{
new HangfireCustomBasicAuthenticationFilter{
User = Configuration.GetSection("HangfireCredentials:UserName").Value,
Pass = Configuration.GetSection("HangfireCredentials:Password").Value
}
});
Please ask any questions in the comments down below.
Top comments (0)