Background Tasks Made Easy with Hangfire and ASP.NET Core

Background Tasks Made Easy with Hangfire and ASP.NET Core

ยท

6 min read

Hangfire is a .NET library that makes it really easy to adding background tasks to your .NET app. It supports one-off "fire and forget" tasks, as well as scheduling recurring tasks. On top of that it supports persistence, so all of your tasks will continue to exist even after restarting your app.

Why do I need background tasks?

Background tasks are important in cases where you need to perform an operation that takes a long time to execute. Without a background task, the response to the user would be delayed while waiting for the task to complete, which leads to a bad user experience. With a background task, the process can continue running in the background and a response can be immediately sent to the user. It's important to note that this only works for processes where you aren't relying on the result of the process to respond to the user.

A good example for a background task is sending emails. For example, imagine a user signs up to a website. They fill in their details, click submit, and get a response from the website saying that registration was successful. Sometime later they get an email confirming their registration. In this case, a background task for sending the email has been started when the user clicks submit, but the website can immediately respond to the user saying that sign up was successful because it does not need to wait for the email task to be completed.

Recurring tasks are also useful in a range of situations. Common use cases might be regularly generating a report, or checking for data integrity.

Using Hangfire in ASP.NET Core

In this example, we will be using a ASP.NET Core 3.1 web application to run Hangfire. Hangfire does work with other .NET project types, but you may need to choose slightly different packages.

Installing and Configuring Hangfire

Start by creating a new ASP.NET Core Web Application. I chose to use the API template, but the MVC template will work fine too.

Go to the NuGet package manager and install the following packages: Hangfire.Core, Hangfire.AspNetCore, and Hangfire.MemoryStorage. We are using in memory storage for ease of use here, but don't use this in production as your tasks will not be persisted after you restart the program. I will show you how to use a SQL database as a persistance method later in this tutorial.

Go to Startup.cs and add the following lines of code:

// rest of startup class

public void ConfigureServices(IServiceCollection services)
{
  // rest of services

  services.AddHangfire(c => c.UseMemoryStorage());
  services.AddHangfireServer();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
  // rest of middleware

  app.UseEndpoints(endpoints =>
  {
    endpoints.MapControllers();
    endpoints.MapHangfireDashboard();
  }
}

And that's all the configuration you need. Run your application and go to https://localhost:{yourPort}/hangfire in your browser. You should now see the Hangfire dashboard.

Hangfire Dashboard

When you start running tasks, you can return to this dashboard to monitor their status.

Creating a simple fire and forget task

You can start off a simple fire and forget task by injecting IBackgroundJobClient into whatever class you want to use it in and running it's Enqueue method.

public class MyClass
{
  private readonly IBackgroundJobClient _backgroundJobClient;

  public MyClass(IBackgroundJobClient backgroundJobClient)
  {
    _backgroundJobClient = backgroundJobClient;
  }

  public bool MyMethod()
  {
    _backgroundJobClient.Enqueue(() => Console.WriteLine("I'm a Fire and Forget job!"));

    return true;
  }
}

In this case, the MyMethod method will return true immediately. The task will then run some time after because it is a background job.

Creating a simple recurring job

A recurring job is a uses IRecurringJobManager instead an requires a few extra parameters to set up.

public class MyClass
{
  private readonly IRecurringJobManager _recurringJobManager;

  public MyClass(IRecurringJobManager recurringJobManager)
  {
    _recurringJobManager = recurringJobManager;
  }

  public bool MyMethod()
  {
    _recurringJobManager.AddOrUpdate(
      "my-id",
      () => Console.Writeline("I'm a recurring job!"),
      "* * * * *");

    return true;
  }
}

The first parameter is the ID of the task. This must be unique as it is used to update and delete the task later. The task can be deleted by using _recurringJobManager.RemoveIfExists("my-id").

The second parameter is the task the you wish to run.

The third parameter is known as a cron string, which is a standardised way that computers use to schedule tasks. In this example, * * * * *, tells Hangfire to run the task once a minute. Cron strings can be confusing, so I often use this website to help me work out what the string should be.

Running more complex tasks

In reality, you're going to want to run more complex tasks than the ones demonstrated above. Hangfire has the ability to inject a service into the task so that you can run a method from that service. For example, imagine we have an Emailer service with a SendEmail method. This method might be dependant on various other services to contruct the email.

public class Emailer 
{
  private readonly IDependancyA _dependancyA;
  private readonly IDependancyB _dependancyB;

  public Emailer(IDependancyA dependancyA, IDependancyB dependancyB)
  {
    _dependancyA = dependancyA;
    _dependancyB = dependancyB;
  }

  public void SendEmail()
  {
    var dataA = _dependancyA.GetData();
    var dataB = _dependancyB.GetData();

    // do other things to send email
  }
}

We then have the option to use either the IBackgroundJobClient or the IRecurringJobManager to execute the SendEmail method. All of the dependencies should be resolved automatically.

_backgroundJobClient.Enqueue<Emailer>(e => e.SendEmail());
_recurringJobManager.AddOrUpdate<Emailer>("my-id", e => e.SendEmail(), "* * * * *");

Adding persistence with a SQL database

In real life applications, you will want to persist your jobs using a database. This way, if your application has to shut down, once it restarts all the existing jobs will still be there. Also, if you have multiple instances of your application running, the database can act as the single source of truth, and prevent tasks being duplicated by each instance.

To use a SQL database, install the Hangfire.SqlServer package and replace services.AddHangfire(c => c.UserMemoryStorage()) in Startup.ConfigureServices with:

services.AddHangfire(c => c
  .UseSqlServerStorage(Configuration.GetConnectionString("HangfireConnection"), new SqlServerStorageOptions
  {
    CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
    SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
    QueuePollInterval = TimeSpan.Zero,
    UseRecommendedIsolationLevel = true,
    DisableGlobalLocks = true
  }));

The above settings are the defaults recommended by Hangfire, you may need to change these for your use case.

Finally go to your appsettings.json file and add the connection string for your Hangfire database.

"ConnectionStrings": {
  "HangfireConnection": "Server=.\\sqlexpress;Database=HangfireTest;Integrated Security=SSPI;"
}

This is an example. Please use your own database credentials.

And that's it. When you submit a job to Hangfire now, the task will be persisted to your SQL database.

Conclusion

In this article I have shown you how to configure Hangfire to run background tasks and recurring jobs in an ASP.NET Core web application. I have also shown you how to inject your own classes into the task to be able to perform more complex operations. Finally I have shown you how to persist those tasks using a SQL database.

I post mostly about full stack .NET and Vue web development. To make sure that you don't miss out on any posts, please follow this blog and subscribe to my newsletter. If you found this post helpful, please like it and share it. You can also find me on Twitter.

Did you find this article valuable?

Support Sam Walpole by becoming a sponsor. Any amount is appreciated!