Unofficial - example of setting up a .NET 8 (Core) Razor/minimal Web app with Hangfire

I’m learning about both ASP.NET Core (migrating from long-time ASP.NET Framework development) and Hangfire. I have a small-scale test project working, based on lots of reading in Hangfire documentation and forum and other sources.

The following is an outline of my approach. Please feel free to comment/critique any of this, or offer suggestions for changes, improvements, or even completely different approaches.

The following is intended as a standalone Hangfire project that both schedules/enqueues Hangfire jobs, and also runs the Hangfire dashboard. You can also have code in your main application that enqueues Hangfire jobs (such as to send an email or process an order) in response to events or user interactions, but I’m not describing that usage here.

I’m using - version 1.8.14 of Hangfire, Visual Studio 2022 and .NET 8.0, and SQL Server 2019/2022.

In Visual Studio 2022, create a new project.

  • ASP.NET Core Web App (Razor Pages)
  • Enter a name, and specify the appropriate location.
  • Select framework “.NET 8.0 (Long Term Support)”, authentication “None”, and “Configure for HTTPS”; Create.
  • Run the newly-created application to test that it works.

In the project in Visual Studio, add NuGet Packages:

  • Hangfire.AspNetCore by Sergey Odinokov (this also installs Hangfire.Core and Hangfire.NetCore, as well as some Microsoft.Extensions packages and Newtonsoft.Json)
  • Hangfire.SqlServer by Sergey Odinokov (if you’re planning to use a SQL Server database)
  • Microsoft.Data.Sqlclient (this is generally recommended instead of the older System.Data.Sqlclient package)

If you are sending emails in your project, you may also want to install the NuGet package “MailKit by Jeffrey Stedfast” (not required for the base project, but recommended for sending emails in jobs with Hangfire)

You will likely want some type of authentication/authorization in this project, but I’m not including that here. You can add the same type of authentication that you for your other applications (Microsoft/Azure Identity, LDAP authentication, table-based, etc.), or if you want a basic authentication process, you can look at using either of these packages intended for Hangfire:

OR GitHub - AbrahamLopez10/Hangfire.Dashboard.BasicAuthenticationFilter: A simpler replacement for the Hangfire.Dashboard.Authorization package.

Open the files “appsettings.json” and “appsettings.Development.json” and add the following to the beginning of both files (immediately after the opening “{”):

"ConnectionStrings": {
"HangfireDBConnection": "Server=(localdb)\MSSQLLocalDB;Database=HangfireLocal;Integrated Security=SSPI;"
},

OR, the appropriate connection string for your database (probably different strings in “appsettings.json” and “appsettings.Development.json”).

Note that entries in “appsettings.Development.json” (when running in development mode) add to (or supersede matching) entries in “appsettings.json”,

In launchSettings.json, add “/PROJECT-NAME” to end of URL for “applicationUrl” under “iisexpress” (replacing “PROJECT-NAME” with your specific project name).
(Optional) Change “Run” setting in Visual Studio to “IIS Express”.

[Note that there is NO Startup.cs file in this minimal style of .NET 6.0 or later Core web application.]

At the very beginning of Program.cs, add these 2 lines:

using Hangfire;
using Hangfire.SqlServer;

In Program.cs, immediately AFTER the line “var builder = WebApplication.CreateBuilder(args);”, add the following:

builder.Services.AddHangfire(configuration => configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireDBConnection"), new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true,
DisableGlobalLocks = true
}));
builder.Services.AddHangfireServer();

In Program.cs, immediately AFTER the line “app.UseAuthorization();” and BEFORE app.MapRazorPages(); add:

app.UseHangfireDashboard("/dashboard-tasks", new DashboardOptions
{
    AppPath = "/hangfire-project",
    DashboardTitle = "Tasks Dashboard"
});

In _Layout.cshtml (in Pages\Shared), in the section with page links, add the following (to add a navigation link) [use the same path and page name as set above]:

<li class="nav-item">
<a class="nav-link text-dark" href="~/dashboard-tasks">Tasks Dashboard</a>
</li>

Add a top-level folder named “Code”

In the “Code” folder, add a file named “applicationStartup.cs”, with the following template (code to be added later):

using Hangfire;
using Hangfire.SqlServer;
internal class applicationStartup
{
    internal static void OnStarted()
    {
        //add code here to add/update/schedule Hangfire jobs, such as:
		BackgroundJob.Schedule(() => Console.WriteLine("Hello, world"));
    }
}

In Program.cs, just before app.Run(), add:

app.Lifetime.ApplicationStarted.Register(applicationStartup.OnStarted);

You should now be able to run this introductory Hangfire project.

Please let me know if you have any comments, suggestions, improvements; or if you see any errors in this.

Here is a complete (barebones) example of Program.cs based on the example above:

using Hangfire;
using Hangfire.SqlServer;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHangfire(configuration => configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireDBConnection"), new SqlServerStorageOptions
{
    CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
    SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
    QueuePollInterval = TimeSpan.Zero,
    UseRecommendedIsolationLevel = true,
    DisableGlobalLocks = true
}));
builder.Services.AddHangfireServer();

builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => {
    _ = endpoints.MapControllers();
});

// Optional - set path for the dashboard page to a non-default path,
//     such as "/dashboard-hangfire" here
// Set AppPath to the base URL of this project
// "DisplayStorageConnectionString" can be set yo true or false to
// display connection string in footer area of dashboard page.
app.UseHangfireDashboard("/dashboard-hangfire", new DashboardOptions
{
    AppPath = "/hangfire-project",
    DefaultRecordsPerPage = 20,
    DisplayStorageConnectionString = true,
    DarkModeEnabled = true,
    DashboardTitle = "Tasks Dashboard"
});

app.MapRazorPages();

// Call separate class to add/update Hangfire jobs/tasks
app.Lifetime.ApplicationStarted.Register(applicationStartup.OnStarted);

app.Run();

And, if you’re running your ASP.NET Core app on IIS, you should follow the instructions here to set it up as always running for .NET Core:

Note that one item mentioned in the linked article (that’s not always listed in other articles) is that you need to install the “Application Initialization Module” in Windows. In my testing, the app pool settings and enabling “Preload” were not sufficient, but after installing the “Application Initialization Module”, my ASP.NET Core application has been running unattended for more than a week.

I just realized if you have fully upgraded to version 1.8 of Hangfire, you should set “CompatibilityLevel.Version_180” in the code above.

See this article for more information: Upgrading to Hangfire 1.8 — Hangfire Documentation

Also, see my separate related post: Thoughts on simple code for scheduling using Hangfire in an ASP.NET Core (minimal) project