P M A J E W S K I

Please Wait For Loading

Background tasks in .NET application - Software Developer's Tour

background task

Background tasks in .NET application

Have you ever thought about doing something in the background to make your request shorter, but kept getting exceptions saying that the DbContext is disposed?

Here is the solution

https://www.hangfire.io/

Hangfire is an open-source framework for .NET that offers a straightforward approach to execute background tasks within your application. It enables the asynchronous execution of jobs and the ability to schedule them to run either at a designated time or on a recurring basis.

Firstly, install the following nuget packages

Hangfire

Hangfire.Core

Hangfire.SqlServer

Microsoft.Data.SqlClient

installed nuget packages

Then configuration, I assume you are at least junior .NET devleoper and know basic configuration in Program.cs

public interface IFakeEmailSenderBackgroundJob
{
    Task Send(string message, string title, string destinationEmail);
}

public class FakeEmailSenderBackgroundJob : IFakeEmailSenderBackgroundJob
{
    public async Task Send(string message, string title, string destinationEmail)
    {
        await File.WriteAllTextAsync($"mails.txt", $"{title}\t{message}\t{destinationEmail}\n");
    }
}

I’ve created simple fake email sender which is saving to file sent mails.

Create SQL Server database named “Hangfire-db”, and then add the following configuration

builder.Services.AddHangfire(x =>
            x.UseSqlServerStorage("Data Source=localhost;Initial Catalog=Hangfire-db;Integrated Security=true;TrustServerCertificate=true;"));
builder.Services.AddHangfireServer();
builder.Services.AddScoped<IFakeEmailSenderBackgroundJob, FakeEmailSenderBackgroundJob>();

in the app section

app.UseHangfireDashboard();

It’s web api project, so let’s create sample endpoint which will be using our FakeEmailSenderBackgroundJob

app.MapPost("/send-mail", (string message, string title, string destinationEmail) =>
{
    return BackgroundJob.Schedule<IFakeEmailSenderBackgroundJob>(x =>
            x.Send(message, title, destinationEmail),
            DateTimeOffset.Now.AddMinutes(1)
        );
})
.WithName("SendMail")
.WithOpenApi();

As you can understand from this code, the endpoint will schedule the sending of the email to occur one minute after the endpoint is executed.

You can go to the /hangfire url in your application and then you should see Hangfire dashboard.

Hangfire dashboard

Send two requests and see what happens.

request swagger
scheduled job hangfire

We can see that our job is scheduled correctly

scheduled job hangfire details

Here are the details regarding our enqueued job.

Wait for a one minute.

hangfire job successed

The enqueued job has been executed, and as we can see, the result is correct. You can perform any desired operations inside your job class. As mentioned earlier regarding the disposed DbContext, you can inject your DbContext or repository directly into the job class.

How about failed tasks?

Let’s change the implementation of the FakeEmailSenderBackgroundJob to the following

public class FakeEmailSenderBackgroundJob : IFakeEmailSenderBackgroundJob
{
    public async Task Send(string message, string title, string destinationEmail)
    {
        throw new Exception();
        await File.WriteAllTextAsync($"mails.txt", $"{title}\t{message}\t{destinationEmail}\n");
    }
}

Let’s check our dashboard now.

failed queue empty

Let’s wait a minute and check again.

hangfire retry

Here’s an interesting observation: the Hangfire job failed to complete successfully and raised an exception, resulting in the job being requeued. Hangfire attempted to execute the job again, with the default behavior allowing for 10 attempts.

We can read number of attempts from information container with blue background

job failed

As you can see, Hangfire attempted to execute its job 10 times before marking it as Failed.

Below you can see failed jobs tab

failed jobs tab

What else Hangfire can do?

Fire-and-Forget Jobs – This job is executed immediately after we execute our method associated with it. Background jobs are useful when you have a large task that consumes a lot of time, and it’s better to perform it asynchronously in the background.

Recurring Jobs – We can set up a cron expression to determine when tasks will be triggered. This is useful, for example, in a SaaS application where we need to check daily if a user has a valid subscription, and if not, suspend it. Similarly, in an application for lawyers, we might want to display court dates with deadlines 10 days in advance. In this case, it’s beneficial to have a background job executed daily that marks important cases. These are just a few examples; there are many other situations where a recurring job would be useful.

Delayed Jobs – We used it in our example. You can set a DateTimeOffset with a delay after which the task will be processed.

Continuations – We can execute a job when its parent job finishes. This is useful when we want to create a pipeline of task execution, especially when certain data needs to be created or prepared beforehand.

Hangfire has more paid options, but I don’t think they are necessary for standard purposes

leave a comment