Hangfire

Task Automation using Hangfire with F#

Updated: 03 September 2023

So I’ve been playing around a bit with Hangfire and Entity Framework thinking about how I can go about building some interesting task automation, and I thought I’d like to explore this concept a bit, at least at a more basic level with F#. So here goes

Prerequisites

Note that these downloads may take a while, they’re kind of big and it’s annoying

Creating the Project

The first thing that we need to do is create a project for the application. In this case we’ll just have a single project and solution file and we can create this with the dotnet core cli. First create a new solution, project, and add the project to the solution

Terminal window
1
mkdir HangfireAutomation
2
cd HangfireAutomation
3
4
dotnet new sln
5
6
dotnet new webapi --language F#
7
8
dotnet sln .\HangfireAutomation.sln add .\HangfireAutomation.fsproj

Since we’ve only got one project at the moment there isn’t really a need for us to add different folders for our solution, although with bigger projects that would ideally be how we’d structure the application

We can then build and run the project using the following commands

Terminal window
1
dotnet run

You can view the running application at https://localhost:<PORT>/weatherforecast

Additionally we can also open the solution and run it from Visual Studio (which I’ll be doing from this point)

Setting up the DB

Now we can set up the database, once you have installed SQL Server Express you can launch the DB from the Connect Now button or by running the following command from Powershell:

1
sqlcmd -S localhost\SQLEXPRESS -E

Next we need to create a database for Hangfire:

1
CREATE DATABASE [HangfireAutomation]
2
GO

You can then list out the databases currently the SQLExpress instance to verify that the database was created:

1
SELECT name, database_id, create_date
2
FROM sys.databases ;
3
GO

Your connection string will be in the installation information, for a default install of SQL Express it should be as follows:

1
Server=localhost\SQLEXPRESS;Database=master;Trusted_Connection=True;

For the database we just created you can therefore use:

1
Server=localhost\SQLEXPRESS;Database=HangfireAutomation;Trusted_Connection=True;

Setting up Hangfire

Now that we have the database set up we can add Hangfire to the application with the following

  1. Add the database connection string to the appsettings.json file as follows:
1
"ConnectionStrings": {
2
"Database": "Server=localhost\\SQLEXPRESS;Database=HangfireAutomation;Trusted_Connection=True;"
3
}
  1. In the startup.fs file we need to configure the Hangfire service, we will need to first import Hangfire
1
open Hangfire

And then in the ConfigureServices method we need to configure the actual service to use SQL Server and the connection string that we added to the appsettings.json file:

1
// Hangfire service configuration
2
services.AddHangfire(
3
fun config ->
4
config.UseSqlServerStorage(
5
this.Configuration.GetConnectionString("Database")
6
) |> ignore
7
) |> ignore

Thereafter we can configure the actual Server and Dashboard. We will use the / route for the dashboard as we don’t have anything running on that. We will do this on the Configure method in the staertup.fs file:

1
app.UseHangfireDashboard "" |> ignore
2
app.UseHangfireServer() |> ignore

Now that we’ve configured hangfire we can run the application, additionally we can change the start URL from Visual Studio under the Project properties to be / so that we launch into the dashboard

  1. Now that Hangfire is up and running correctly we can modify the Configure method’s constructor to include the IBackgroundClient dependency so we can create a job as follows:
1
member this.Configure(app: IApplicationBuilder, env: IWebHostEnvironment, backgroundJobClient: IBackgroundJobClient) =

And then in the Configure method we can call the backgroundJobClient.Enqueue method to start a job. Note that the job takes in a lambda for the function we want to execute

1
backgroundJobClient.Enqueue(
2
fun () ->
3
Console.WriteLine "Startup Job"
4
) |> ignore

Once we’ve added the task we can run the application again and we will see that the job has executed

Next up we can just set up another recurring job using the static RecurringJob.AddOrUpdate function in the Configure method as well:

1
RecurringJob.AddOrUpdate(
2
fun () ->
3
Console.WriteLine "Recurring Job"
4
, Cron.Minutely
5
)

Once we are done with the above, the overall startup.fs file should look something like:

1
namespace HangfireAutomation
2
3
open System
4
open Microsoft.AspNetCore.Builder
5
open Microsoft.AspNetCore.Hosting
6
open Microsoft.Extensions.Configuration
7
open Microsoft.Extensions.DependencyInjection
8
open Microsoft.Extensions.Hosting
9
open Hangfire
10
11
type Startup private () =
12
new (configuration: IConfiguration) as this =
13
Startup() then
14
this.Configuration <- configuration
15
16
// This method gets called by the runtime. Use this method to add services to the container.
17
member this.ConfigureServices(services: IServiceCollection) =
18
// Add framework services.
19
services.AddControllers() |> ignore
20
21
// Hangfire service configuration
22
services.AddHangfire(
23
fun config ->
24
config.UseSqlServerStorage(
25
this.Configuration.GetConnectionString("Database")
26
) |> ignore
27
) |> ignore
28
29
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
30
member this.Configure(app: IApplicationBuilder, env: IWebHostEnvironment, backgroundJobClient: IBackgroundJobClient) =
31
if (env.IsDevelopment()) then
32
app.UseDeveloperExceptionPage() |> ignore
33
34
app.UseHttpsRedirection() |> ignore
35
app.UseRouting() |> ignore
36
37
app.UseAuthorization() |> ignore
38
39
app.UseEndpoints(fun endpoints ->
40
endpoints.MapControllers() |> ignore
41
) |> ignore
42
43
app.UseHangfireDashboard "" |> ignore
44
app.UseHangfireServer() |> ignore
45
46
backgroundJobClient.Enqueue(
47
fun () ->
48
Console.WriteLine "Startup Job"
49
) |> ignore
50
51
RecurringJob.AddOrUpdate(
52
fun () ->
53
Console.WriteLine "Recurring Job"
54
, Cron.Minutely
55
)
56
57
member val Configuration : IConfiguration = null with get, set

And your appsettings.json like:

1
{
2
"Logging": {
3
"LogLevel": {
4
"Default": "Warning",
5
"Microsoft.Hosting.Lifetime": "Information"
6
}
7
},
8
"AllowedHosts": "*",
9
"ConnectionStrings": {
10
"Database": "Server=localhost\\SQLEXPRESS;Database=HangfireAutomation;Trusted_Connection=True;"
11
}
12
}