How to config Hangfire with Asp.Net 5?

Hi to everyone, I’m starting to use Hangfire with a new Asp.Net 5 project, but following instructions I have been blocked registering app.UseHangfireServer() and app.UseHangfireDashboard() into Startup class.
With the new framework the method Configure(IAppBuilder app) is changed to Configure(IApplicationBuilder app), and I can’t find any right extension. How can I canfigure Hangfire with the new interface?
Other question, what about Hangfire’s plans for support natively also Asp.Net 5 in future?

There is currently an issue related to this: https://github.com/HangfireIO/Hangfire/issues/267

When I tried to use Hangfire in ASP.Net 5, I had to do a bit of a dirty hack I’m afraid to say. (Or two)

First of all I had:

private static void ConfigureHangfire(IApplicationBuilder app, string dashboardPath = "/hangfire")
{
	app.UseAppBuilder(builder =>
	{
		GlobalConfiguration.Configuration.UseSqlServerStorage("<ConnectionString>");

		builder
			.UseHangfireDashboard(dashboardPath);
			//.UseHangfireServer();

		// Todo - Remove this dirty hack:
		var server = new BackgroundJobServer(new BackgroundJobServerOptions(), JobStorage.Current);
	});
}

The IApplicationBuilder.UseAppBuilder extension is as follows:

using AppFunc = Func<IDictionary<string, object>, Task>;

public static class IApplicationBuilderExtensions
{
	public static IApplicationBuilder UseAppBuilder(this IApplicationBuilder app, Action<IAppBuilder> configure)
	{
		return app.UseOwin(addToPipeline =>
		{
			addToPipeline(next =>
			{
				AppBuilder appBuilder = new AppBuilder();
				appBuilder.Properties["builder.DefaultApp"] = next;

				configure(appBuilder);

				return appBuilder.Build<AppFunc>();
			});
		});
	}
}

So the extension method resolves the first issue regarding IApplicationBuilder. This is actually stolen from here: https://github.com/aspnet/Entropy/blob/dev/samples/Owin.IAppBuilderBridge/KAppBuilderExtensions.cs

The second hack is around the UseHangfireServer extension. This sets up an event on the “host.OnAppDisposing” or “server.OnDispose” application builder properties which the current ASP.Net 5 development webservers do not support. Manually creating an instance on BackgroundJobServer resolves this, although you should ensure it doesn’t end up getting automatically disposed.

Hope this helps.

And here is another implementation that does not reference Owin library and its IAppBuilder interface. It also uses IApplicationLifecycle interface to support graceful shutdown.

using System;
using Hangfire;
using Hangfire.Annotations;
using Hangfire.Dashboard;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.Framework.DependencyInjection;

namespace WebApplication2
{
    public static class ApplicationBuilderExtensions
    {
        public static IApplicationBuilder UseHangfireServer([NotNull] this IApplicationBuilder builder)
        {
            return builder.UseHangfireServer(new BackgroundJobServerOptions());
        }

        public static IApplicationBuilder UseHangfireServer(
            [NotNull] this IApplicationBuilder builder,
            [NotNull] BackgroundJobServerOptions options)
        {
            return builder.UseHangfireServer(options, JobStorage.Current);
        }

        public static IApplicationBuilder UseHangfireServer(
            [NotNull] this IApplicationBuilder builder,
            [NotNull] BackgroundJobServerOptions options,
            [NotNull] JobStorage storage)
        {
            if (builder == null) throw new ArgumentNullException("builder");
            if (options == null) throw new ArgumentNullException("options");
            if (storage == null) throw new ArgumentNullException("storage");

            var server = new BackgroundJobServer(options, storage);

            var lifetime = builder.ApplicationServices.GetRequiredService<IApplicationLifetime>();
            lifetime.ApplicationStopped.Register(server.Dispose);

            return builder;
        }

        public static IApplicationBuilder UseHangfireDashboard([NotNull] this IApplicationBuilder builder)
        {
            return builder.UseHangfireDashboard("/hangfire");
        }

        public static IApplicationBuilder UseHangfireDashboard(
            [NotNull] this IApplicationBuilder builder,
            [NotNull] string pathMatch)
        {
            return builder.UseHangfireDashboard(pathMatch, new DashboardOptions());
        }

        public static IApplicationBuilder UseHangfireDashboard(
            [NotNull] this IApplicationBuilder builder,
            [NotNull] string pathMatch,
            [NotNull] DashboardOptions options)
        {
            return builder.UseHangfireDashboard(pathMatch, options, JobStorage.Current);
        }

        public static IApplicationBuilder UseHangfireDashboard(
            [NotNull] this IApplicationBuilder builder,
            [NotNull] string pathMatch, 
            [NotNull] DashboardOptions options, 
            [NotNull] JobStorage storage)
        {
            if (builder == null) throw new ArgumentNullException(nameof(builder));
            if (pathMatch == null) throw new ArgumentNullException(nameof(pathMatch));
            if (options == null) throw new ArgumentNullException(nameof(options));
            if (storage == null) throw new ArgumentNullException(nameof(storage));

            return builder.Map(pathMatch, subApp =>
            {
                subApp.UseOwin(next =>
                {
                    next(MiddlewareExtensions.UseHangfireDashboard(options, storage, DashboardRoutes.Routes));
                });
            });
        }
    }
}
2 Likes

Thank you for your reply, I will try to apply your suggestion soon. What about your plan to solve the issue #267?

I could remove that dependency only in 2.0, but as I know, this is not an issue.

Thanks for providing this solution (didnt check it yet) I was just searching for a solution like this…

odinserj do you have a sample how to use the built in DI service in asp.net 5 with hangfire?

Nope, but I’m thinking how to seamlessly plug in ASP.NET 5’s service location and logging into Hangfire. There may be some problems with them, because background processing is detached from request handling logic.

The very last method in this solution does in fact use OWIN. Is there a solution without using OWIN?

Nope, there is no any solution without using OWIN. Why do you want to get rid of it? Dashboard should somehow interact with request and response data, without OWIN it is possible through System.Web (we say no to System.Web), or via the new HTTP abstractions that come with ASP.NET 5.

I don’t want to have two dashboards in a project (one for OWIN and one for ASP.NET 5), and I don’t want to add yet another abstraction for abstraction to support different runtimes. OWIN is supported by all the ASP.NET worlds, why don’t use it?

@odinserj I finally got it to work this morning by including this package in my package.json:

"Microsoft.AspNet.Owin": "1.0.0-beta5"

I had been including just the regular “Owin” package which was causing it not to build.

2 Likes

@robfarmergt, thank you for letting me know! So this was the only problem of using OWIN?

Yes. That was the only issue and it only affected the dashboard. I can now view the dashboard and have jobs running. Thanks for your work on this project!

2 Likes

So following this thread I have been able to get Hangfire up and running in a DNX application. Is this still the best way to set this up? Just want to double check to see if there isn’t anything baked in now before I build on top of this.

Hello @odinserj,

First of all thank you very much for provided solution it worked like a charm after adding Owin package like @robfarmergt mentioned.

Unfortunately after going to beta8 hangfire (at least the dashboard) doesn’t work any more. No errors, nothing in output, just white screen when going to url/hangfire.

I tried to set up everything from scratch in new solution but the result remains the same. I can provide you with some code, but I believe it will be enough to create new beta8 solution and add Hangfire without any tasks to reproduce the issue.
See list of breaking changes, maybe something happend to libraries which hangfire is using;


Thank you in advance for any input in this.

Hello,
I managed to solve it. I do not know why, but after upgrade to beta8 all request to the dashboard returned 401 (Unauthorized). This was strange becouse I didn’t change anything with the authorization. I’ve implemented dummy authorization filter using;


and the dashboard works like a charm. Best regards.

How I got @odinserj’s ASP.NET 5 (RC1 Update 1 and EF7) implementation to work on Azure (including the Dashboard)

First via Nuget I added the two packages

Hangfire, version 1.5.3
Hangfire.Dashboard.Authorization, version 2.1.0

also added “Microsoft.AspNet.Owin”: “1.0.0-rc1-final” Nuget pacjage mentioned by @robfarmergt

Next I added ApplicationBuilderExtensions class shown by @odinserj

Next I modified the Startup.cs file
(I have removed the code above app.UseMvc(routes => … ) to shorten the example)

I added “IServiceProvider serviceProvider” in to the parameters of this method which is passed in by ASP.NET 5 DI

Next added the two new methods and called them at the end of the Configure method

ConfigHangfire(app);
the code to add and configure Hangfire including an AuthorizationFilters

await CreateRoles(serviceProvider);
Creates the Admin role used by the Hangfire AuthorizationFilter
and assigns a default user (Your-Default-Admin-UserName) to the Admin role
who can view the Hangfire Dashboard

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider serviceProvider)
    { 

        app.UseMvc(routes =>
        {
            routes.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}");
        });

        ConfigHangfire(app);

        await CreateRoles(serviceProvider);
    }

    private void ConfigHangfire(IApplicationBuilder app)
    {
        JobStorage.Current = new SqlServerStorage(Configuration["Data:DefaultConnection:ConnectionString"], new SqlServerStorageOptions());

        app.UseHangfireServer();

        app.UseHangfireDashboard("/Hangfire", new DashboardOptions { AuthorizationFilters = new List<IAuthorizationFilter> { new AuthorizationFilter { Roles = "Admin" } } });

        // RecurringJob.AddOrUpdate(() => Debug.WriteLine("Hangfire Test job"), Cron.Minutely);
    }

    private async Task CreateRoles(IServiceProvider serviceProvider)
    {
        var roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
        var userManager = serviceProvider.GetRequiredService<UserManager<ApplicationUser>>();

        string[] roleNames = { "Admin", "Editor", "Guest" };

        IdentityResult roleResult;

        foreach (var roleName in roleNames)
        {
            var roleExists = await roleManager.RoleExistsAsync(roleName);
            if (!roleExists)
            {
                roleResult = await roleManager.CreateAsync(new IdentityRole(roleName));
            }
        }

        var user = await userManager.FindByNameAsync("**Your-Default-Admin-UserName**");
        if (!await userManager.IsInRoleAsync(user, "Admin"))
        {
            await userManager.AddToRoleAsync(user, "Admin");
        }
    }

I am sure this could be done in a neater/better fashion, but it is how I got it working and able to see the Dashboard when I hosted the WebApp in Azure

1 Like

Wonderful reply dotnetnoobie. This worked for me…with the following notes that you might even want to incorporate into your walkthrough:

  1. I had to remove the DNX50Core dependency. My project just won’t built with it. I guess this is something that hangfire will improve in the future
  2. It’s worth mentioning that the AuthorizationFilter class that you refer to in DashboardOptions will not exist unless you implement it
  3. The CreateRoles method will fail if the user account listed does not exist, the paster will need to add their own error handling :slightly_smiling:

Is anyone else seeing a new Hangfire server being created each time the app is started? In the dashboard, under the Servers tab, I see multiple entries when I would expect just one.

In fact, in my ASP.NET 4.5 site, it works fine, however in the ASP.NET 5 site (while following the above steps) I see an entry each time the app is started.