Configuring Hangfire with F#
I’m writing this blog post in case someone else runs into similar issues. What started as a simple “add Hangfire” task on my to-do list turned into several loops of yak shaving and wasted time.
What is Hangfire
Hangfire provides “an easy way to perform background processing in .NET applications”. Think of it as a tool for scheduled jobs that run in the background as part of your app deployment.
The Issue
The configuration code is very easy to use, and I’ve been using it successfully in one of my web apps where the web portion is written in C#. However, issues arose when I tried to use Hangfire for my other web app that’s fully written in F#.
The issue is simple. Here’s an example where I wanted to create a recurring job that runs every day at 4 PM:
RecurringJob.AddOrUpdate<Services>(
recurringJobId="fullrun",
methodCall=(fun (s:Services) -> s.FullRun()),
cronExpression=Cron.Daily(16, 00),
options=rjo
)
If you try to compile this, you’ll get a cryptic compilation error:
No overloads match for method 'AddOrUpdate'.
Known types of arguments: recurringJobId: string * methodCall: (unit -> Task<unit>) * cronExpression: string
Available overloads:
- RecurringJob.AddOrUpdate(recurringJobId: string, methodCall: Expression<Action>, cronExpression: Func<string>) : unit // Argument 'methodCall' doesn't match
- RecurringJob.AddOrUpdate(recurringJobId: string, methodCall: Expression<Action>, cronExpression: Func<string>, ?timeZone: TimeZoneInfo, ?queue: string) : unit // Argument 'methodCall' doesn't match
- RecurringJob.AddOrUpdate(recurringJobId: string, methodCall: Expression<Action>, cronExpression: string) : unit // Argument 'methodCall' doesn't match
- RecurringJob.AddOrUpdate(recurringJobId: string, methodCall: Expression<Action>, cronExpression: string, ?timeZone: TimeZoneInfo, ?queue: string) : unit // Argument 'methodCall' doesn't match
- RecurringJob.AddOrUpdate(recurringJobId: string, methodCall: Expression<Func<Task>>, cronExpression: Func<string>) : unit // Argument 'methodCall' doesn't match
- RecurringJob.AddOrUpdate(recurringJobId: string, methodCall: Expression<Func<Task>>, cronExpression: Func<string>, ?timeZone: TimeZoneInfo, ?queue: string) : unit // Argument 'methodCall' doesn't match
- RecurringJob.AddOrUpdate(recurringJobId: string, methodCall: Expression<Func<Task>>, cronExpression: string) : unit // Argument 'methodCall' doesn't match
- RecurringJob.AddOrUpdate(recurringJobId: string, methodCall: Expression<Func<Task>>, cronExpression: string, ?timeZone: TimeZoneInfo, ?queue: string) : unit // Argument 'methodCall' doesn't match
- RecurringJob.AddOrUpdate<'T>(recurringJobId: string, methodCall: Expression<Action<'T>>, cronExpression: Func<string>) : unit // Argument 'methodCall' doesn't match
- RecurringJob.AddOrUpdate<'T>(recurringJobId: string, methodCall: Expression<Action<'T>>, cronExpression: Func<string>, ?timeZone: TimeZoneInfo, ?queue: string) : unit // Argument 'methodCall' doesn't match
- RecurringJob.AddOrUpdate<'T>(recurringJobId: string, methodCall: Expression<Action<'T>>, cronExpression: string) : unit // Argument 'methodCall' doesn't match
- RecurringJob.AddOrUpdate<'T>(recurringJobId: string, methodCall: Expression<Action<'T>>, cronExpression: string, ?timeZone: TimeZoneInfo, ?queue: string) : unit // Argument 'methodCall' doesn't match
- RecurringJob.AddOrUpdate<'T>(recurringJobId: string, methodCall: Expression<Func<'T,Task>>, cronExpression: Func<string>) : unit // Argument 'methodCall' doesn't match
- RecurringJob.AddOrUpdate<'T>(recurringJobId: string, methodCall: Expression<Func<'T,Task>>, cronExpression: Func<string>, ?timeZone: TimeZoneInfo, ?queue: string) : unit // Argument 'methodCall' doesn't match
- RecurringJob.AddOrUpdate<'T>(recurringJobId: string, methodCall: Expression<Func<'T,Task>>, cronExpression: string) : unit // Argument 'methodCall' doesn't match
- RecurringJob.AddOrUpdate<'T>(recurringJobId: string, methodCall: Expression<Func<'T,Task>>, cronExpression: string, ?timeZone: TimeZoneInfo, ?queue: string) : unit // Argument
The Solution
It seems that the methodCall
parameter is what the compiler doesn't like. The solution is painfully simple: cast the return of s.FullRun()
(which was returning Task<unit>
) to Task
:
RecurringJob.AddOrUpdate(
recurringJobId="fullrun",
methodCall=(fun () -> service.SomeBackgroundWork() :> Task),
cronExpression=Cron.Daily(17, 00)
) |> ignore
Adding :> Task
does the trick. Now everything compiles and works as expected.
Silly stuff. There may be different ways to approach this, but this solution got me past the issue and running. Hopefully, this post will save someone else some time in the future.