close up picture of cogs

Hangfire background tasks for .NET – Part 2: Using the client

In this post, we will use Hangfire’s client to create background jobs in the storage. I’ll use SQL Server as storage.

Set-up and dependency installs

I’ll create a new folder and console application using the dotnet command dotnet new console. Afterwards, we’ll add the NuGet packages we need:

dotnet add package Hangfire.Core
dotnet add package Hangfire.SqlServer
dotnet add package Microsoft.Data.SqlClient

Notice that I did not install the meta package Hangfire because this includes the integrations for ASP.NET which we do not need since this is a console application. When using SQL Server as storage we also need Hangfire.SqlServer and the ADO.NET driver for the database. The reason why Hangfire.SqlServer does not simply have a static dependency on the driver, which would install the dependency automatically is, that Hangfire works with other driver packages as well and resolves them at runtime.

Configuring Hangfire

Configuration needs to be done before we can use any of Hangfire’s functionalities, otherwise an exception will be thrown. At the very least you’ll need to tell Hangfire which storage you wish to use. In our application, we will tell Hangfire to use SQL Server as storage. You do this via the GlobalConfiguration class which has a static property Configuration. This is provided by the Hangfire.Core package and all core components read this central configuration: the client, the server and the dashboard. From the Hangfire.SqlServer package we get a convenient extension method to configure the connection string for the database we want to use.

using Hangfire;

GlobalConfiguration.Configuration
    .UseSqlServerStorage(@"
        Server=.;
        Database=MyApp;
        Integrated Security=True;
        TrustServerCertificate=True;
    ");

One thing you need to be aware of is that everytime you access the Configuration property, a new object will be assigned to the property which will effectively erase all previously added configuration. If you need to access the configuration from multiple places, keep that in mind and manage the pointers to the object yourself or even better do all configuration in one place using the fluent API.

When you run this code, Hangfire will create the following tables in the MyApp database in its own Hangfire schema if it has the permission to do so:

  • AggregatedCounter,
  • Counter,
  • Hash,
  • Job,
  • JobParameter,
  • JobQueue,
  • List,
  • Schema,
  • Server,
  • Set and
  • State

You need to create the database first with CREATE DATABASE MyAppor use a database that already exists.

As Hangfire is developed the schema will change and Hangfire ensures compatibility by saving the version number of the installed schema into the Schema table.

Client interface

To interface with your jobs you use the static methods of the Hangfire.BackgroundJob class. This class is a convenience wrapper around Hangfire.BackgroundJobClient which you can also use if necessary. For most scenarios, however, the wrapper will suffice. The client’s only job is to create jobs, place them in storage and update job data in storage if we change a job via the client API (e.g. delete it or reschedule it). It does not process any jobs nor does it provide the dashboard UI.

Create a job for immediate execution

string jobId = BackgroundJob.Enqueue(
() => System.Console.WriteLine("Hello from Hangfire server!")
);
System.Console.WriteLine($"Enqueued Job { jobId }");

//outputs
//Enqueued Job 1

This will create a new job in storage with state Enqueued which you can verify by looking into the database: SELECT * FROM [HangFire].[Job] [j]. Note that the job we enqueued is not executed because we do not yet have a Hangfire server running.

Create a job with deferred execution

string jobId = BackgroundJob.Schedule(
() => System.Console.WriteLine("Hello from Hangfire server!"),
TimeSpan.FromMinutes(5)
);
System.Console.WriteLine($"Enqueued Job { jobId }");

jobId = BackgroundJob.Schedule(
() => System.Console.WriteLine("Hello from Hangfire server!"),
DateTimeOffset.Now.AddDays(1)
);
System.Console.WriteLine($"Enqueued Job { jobId }");

//outputs
//Enqueued Job 2
//Enqueued Job 3

This will create a job that executes in five minutes and another job that will execute in a day. When observing the values created in the database we can see that the state is now Scheduled instead of Enqueued.

Create a job that executes after another job finishes

string firstJobId = BackgroundJob.Enqueue(
() => System.Console.WriteLine("Hello from Hangfire Server!")
);
string secondJobId = BackgroundJob.ContinueJobWith(
firstJobId,
() => System.Console.WriteLine("Hello from Hangfire Server!")
);

The first call does the same as we’ve seen in the first example. The second call also creates a job in [HangFire].[Job] but in the Awaiting state as its parent job is not finished yet. The ID of the parent job can be found in the Data column of the table [HangFire].[State].

Change the state of existing jobs

bool success = BackgroundJob.Reschedule(jobId, TimeSpan.FromMinutes(5));
success = BackgroundJob.Requeue(jobId);
success = BackgroundJob.Delete(jobId);

success = BackgroundJob.Reschedule(jobId, TimeSpan.FromMinutes(5), ScheduledState.StateName);
success = BackgroundJob.Requeue(jobId, FailedState.StateName);
success = BackgroundJob.Delete(jobId, SucceededState.StateName);

The first three statements set the job’s state to Scheduled, Enqueued or Deleted no matter the current job state.

The second set of statements checks if the job is in the provided state and only then changes its state to the desired one.

Run jobs based on Cron schedules

RecurringJob.AddOrUpdate(
recurringJobId,
() => Console.WriteLine("Daily Hello from Hangfire server at xx:15!"),
Cron.Hourly(15)
);

RecurringJob.AddOrUpdate(
recurringJobId,
() => Console.WriteLine("Daily Hello from Hangfire server at xx:00!"),
Cron.Hourly
);

RecurringJob.AddOrUpdate(
recurringJobId,
() => Console.WriteLine("Daily Hello from Hangfire server at xx:30!"),
"30 * * * *"
);

The ID is to be chosen by you and can be any string. The parameters for recurring jobs are stored in the [HangFire].[Hash] table. When the execution starts a new instance of this job will be enqueued by the Hangfire server. Of course, you can also delete recurring jobs or trigger them to be executed “now”.

RecurringJob.RemoveIfExists(recurringJobId);

RecurringJob.TriggerJob(recurringJobId);

Summary

Use the BackgroundJob class for managing jobs that should execute once.

.Enqueue()create a fire and forget job
.Schedule()create a job to be executed after defined some time
.ContinueJobWith()create a job that is executed after another job finishes, if not stated otherwise the parent job must finish in the state “Hangfire.States.SucceededState”
.Reschedule()change the state of an existing job to “Hangfire.States.ScheduledState”
.Requeue()change the state of an existing job to “Hangfire.States.EnqueuedState”
.Delete()change the state of an existing job to “Hangfire.States.DeletedState”
Static methods on the BackgroundJob class

Use the RecurringJob class for managing jobs that should run on a schedule.

.AddOrUpdate()add a new recurring job or alter it
.RemoveIfExists()remove the job with the provided id
.TriggerJob()execute a previously added recurring job now
Static methods on the RecurringJob class

Join me in part 3 where we’ll create a Hangfire server to execute our jobs.

Photo by Mark Hindle on Unsplash