Hangfire for Background Task Processing in ASP.net

posted in: Tools | 0

Have you come across a situation where background processing of tasks is required for you ASP.net application? Well, there are many instances like sending emails, it is good to have a background processing. This could be for two reasons first you don’t want to make the user wait for a bit lengthy action complete which could be completed in the background. Second, you require running a set of tasks on a periodic interval just like cron jobs as in Linux. You could use Hangfire as a job scheduler for ASP.net applications.

Hangfire is an open source software released under the terms of the GNU Lesser General Public License. In this guide, I have covered the basic installation of Hangfire and using it efficiently.

Installation

Hangfire is available on Nuget, you could install it using the following commands in Package manager console. In this case we have use SQL server hence installing package Hangfire.SqlServer.

[csharp]Install-Package Hangfire
Install-Package Hangfire.SqlServer
[/csharp]

Configuration

Hangfire uses Database for storing jobs and other information, you could either use your application Database for storing Hangfire or use a separate one. Hangfire would automatically create the required table on the first use. Ideally, the background server is started at the start of the application, but you may choose to start it later.

Following block of code shows the initial configuration and starting background server.

[csharp]using Hangfire;
using System;
using System.Web;
using Hangfire;

namespace WebApplication1
{
public class Global : HttpApplication
{
private BackgroundJobServer _backgroundJobServer;

protected void Application_Start(object sender, EventArgs e)
{
GlobalConfiguration.Configuration
.UseSqlServerStorage("DbConnection");

_backgroundJobServer = new BackgroundJobServer();
}

protected void Application_End(object sender, EventArgs e)
{
_backgroundJobServer.Dispose();
}
}
}
[/csharp]

Background Method Processing

Hangfire provides a simple fire and forget mechanism to create and schedule a job for your ASP.net application. Jobs could be enqueued using BackgroundJob.Enqueue() method.

[csharp]BackgroundJob.Enqueue(() => EmailProcessing.Process());[/csharp]

Alternatively, you could create an instance of BackgroundJobClient, and use it for firing jobs. Just fire the jobs and continue with the processing. Hangfire would enqueue the jobs and process it.

[csharp] BackgroundJobClient client = new BackgroundJobClient();
client.Enqueue(() => new Purchase().verify(order.id));
[/csharp]

In case you are having a large number of jobs to be scheduled or jobs consuming much time, you could configure a number of worker threads for processing.

[csharp] var options = new BackgroundJobServerOptions { WorkerCount = Environment.ProcessorCount * 5 };
app.UseHangfireServer(options);
[/csharp]

Performing recurring tasks – ASP.net Job Scheduler

Just like Linux you could schedule a cron job. Hangfire could be used as ASP.net Job Scheduler. You could use Cron class with the following methods :Minutely, Hourly, Daily, Weekly, Monthly, Yearly. Alternatively, you could schedule jobs using Linux like cron expression. Below is the cron expression format with examples

 

[bash] +—————- minute (0 – 59)
| +————- hour (0 – 23)
| | +———- day of month (1 – 31)
| | | +——- month (1 – 12)
| | | | +—- day of week (0 – 6) (Sunday=0 or 7)
| | | | |
* * * * *

[/bash]

Examples
*/5 * * * * – Every 5 minutes
0 */5 * * * – Every 5 hours
0 0 * * 5 – Every 5th day
0 0 * * Fri – Every 5th day

Here’s and example to schedule jobs using RecurringJob class and the cron expressions explained above.

[csharp]RecurringJob.AddOrUpdate(() => new Promotions().SendEmail(), Cron.Daily);

RecurringJob.AddOrUpdate(() => new Promotions().SendEmail(), "*/30 * * * *", queue: "emailqueue");
[/csharp]

Using Multiple Servers and Queues with Hangfire

It is possible to use multiple servers for job processing using Hangfire, just use the same DB connection across the servers. Apart from distributed processing of jobs across the servers, hangfire has a mechanism of defining queues for processing the jobs. Meaning that you could specify what queues the server would process to distribute the load or according to server capabilities.

[csharp] var options = new BackgroundJobServerOptions
{
Queues = new[] { "critical", "default", "emailqueue" }
};

app.UseHangfireServer(options);
[/csharp]

While firing or scheduling a job, you could select the queue to process the job as in the example above.

Logging

Hangfire comes with a feature where you do not require make any extra efforts if you are using any of these loggers.

  1. Serilog
  2. NLog
  3. Log4Net
  4. EntLib Logging
  5. Loupe
  6. Elmah

Hangfire would automatically recognize the logger used. In our case we used log4net and hangfire automatically started logging once logging framework was in place.

Hangfire – Use Cases

Here’s how we have used Hangfire for scheduling jobs in ASP.net for an implementation.

  1. Sending emails in the background.
  2. Promotions : Checking if any promotions are applicable and taking necessary actions if applicable.

User Initiated Background Tasks – Emails in the background

Consider a scenario where you need to send an email to the user after processing some time-consuming task. You could send the email in the background and display content to the user since this would help to reduce the time required to serve pages.

Hangfire could do it with an ease, here’s what could be done

  1. Do the basic configuration and start the background processing server
  2. While user performs an action, create a Background job and let the processing server process it

Hangfire for Recurring tasks – Promotions

For a scenario where a recurring task needs to be performed after a regular interval, Hangfire could schedule jobs executing them at the intervals you have defined.

For example, a scenario where you need to scan through all the accounts and send emails to accounts eligible for promotional offers. And you require it to be done twice a day. This is how you could handle it with Hangfire.

Start the background server with the application start, and schedule the job with recurrence as 12 hours.

 

Working with Reentrancy

While executing a long running task, it may happen so that the Processing Server is restarted while the task is still processing. Though Hangfire has a mechanism to start processing the same task, it does not start the execution from where it stopped instead from the start. You might want to avoid such situations while working in a production scenario involving critical data.

We had encountered a situation where promotional emails had to be sent out to a large number of audience, and the Hangfire server was restarting every 30 minutes. Hence the email sending task was restarted too, sending emails repeatedly to the same addressee.

To avoid such scenarios try using persistent storage to store the current state of the task. So even if the task restarted you could read the persistent storage to figure out where to start from. Here’s what we did in our implementation:

  1. Created a table (Task_Details) to store the task details, so every time a promotion task (Promotions.Main) was created a database entry was created. And task would return a unique task_id (Primary key of Task_Details) and Enqueue actual processing method with the task_id.
  2. Let the ProcessTask continue the actual processing and update the Persistent Storage on regular intervals. Now even when the Hangfire server is restarted, the processing would be restarted from where it stopped.

Sample Code:

[csharp] class Promotions
{
public void CreateTask()
{
//Create a Task here and get the TaskId
BackgroundJob.Enqueue(() => new Promotions().ProcessTask(TaskId));
}
public void ProcessTask(int TaskId)
{
//Read the Persistent Storage to get previous processing details if any.
//Start processing and update the Persistent Storage on regular intervals
}
}
[/csharp]

Hangfire Dashboard

Hangfire comes with a dashboard that allows monitoring of Background Jobs and Servers and do a lot more. Here’s what you can do with the dashboard

  1. Monitor Scheduled Jobs
  2. View Server details(Worker Count, Queueus, Running Status)
  3. Reenqueue failed jobs
  4. Trigger Scheduled Jobs
  5. View Job History

Enabling Hangfire Dashboard is just adding a line of code in your Startup class. By Default hangfire dashboard would be mapped to http://your-app-url/hangfire, but you could also configure it.

[csharp] public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseHangfireDashboard();
}
}
[/csharp]

Configuring Authorization on Hangfire Dashboard

For accessing Hangfire Dashboard from a remote server, you would require configuring Authorization. You can do this by either using Hangfire.Dashboard.Authorization. package or creating an Authorization Filter of your own.

1. Create a HangfireAuthorizationFilter implementing IDashboardAuthorizationFilter. Here would be main logic for returning if the user is authorised. In the example below, only Authenticated Users in Role ‘admin’ would be allowed.
[csharp] public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter
{
public bool Authorize([NotNull] DashboardContext context)
{
var owincontext = new OwinContext(context.GetOwinEnvironment());
if (owincontext.Authentication.User.Identity.IsAuthenticated && owincontext.Authentication.User.IsInRole("Admin"))
return true;
else
return false;
return true;
}
}
[/csharp]

2. Add the following in your Startup class. Here I have used the Authorization Filter (HangfireAuthorizationFilter) created above and configured the dashboard on http://your-app-url/hangfiredashboard.

[csharp] public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
GlobalConfiguration.Configuration
.UseSqlServerStorage("DefaultConnection");
app.UseHangfireDashboard("/hangfiredashboard", new DashboardOptions
{
Authorization = new[] { new HangfireAuthorizationFilter() }
});
app.UseHangfireServer();

}
[/csharp]

How have you used Hangfire, or planning to use it? Would love to hear about your implementations, share it in the comments below.

Leave a Reply

Your email address will not be published. Required fields are marked *