.NET 6 - Background Jobs with Hangfire 🔥🔥🔥
Tags: dotnet, tutorial, programming, backgroundjobs
Published: 2022-11-03 05:38:33

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:
{% embed https://youtu.be/Xafuut_KAB0 %}

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

```csharp
dotnet new webapi -n "FireAPI"
```

Next we need to install the packages

```csharp
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

```json
"ConnectionStrings": {
    "DefaultConnection": "app.db"
  }
```

Now we need to configure our Hangfire configuration inside our Program.cs

```csharp
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

```csharp
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.

```csharp
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 

```csharp
namespace FireApp.Services;

public interface IServiceManagement
{
    void SendEmail();
    void UpdateDatabase();
    void GenerateMerchandise();
    void SyncRecords();
}
```

Now let us build an implementation of this interface

```csharp
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

```csharp
builder.Services.AddScoped<IServiceManagement, ServiceManagement>();
```

Now let us add a long running service which we want to execute every 4h

```csharp
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](https://www.freeformatter.com/cron-expression-generator-quartz.html)

Now let us update the controller to add the services 

```csharp
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

```csharp
"HangfireCredentials": {  
    "UserName": "mohamad",  
    "Password": "Passw0rd"  
  }
```

And we need to update the program.cs to the following

```csharp
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.