Hangfire: IHostedService in asp.net core 2.0

Created on 6 Aug 2017  路  11Comments  路  Source: HangfireIO/Hangfire

Just read about this: https://www.stevejgordon.co.uk/asp-net-core-2-ihostedservice
And was wondering if this would impact hangfire's integration story in any way for asp.net core 2.0

dotnetcore problem

Most helpful comment

This feature was added in the latest betas of 1.7.0 so you can register Hangfire Server as a hosted service in the .NET Core application as shown below. The current gotcha is you should pass options as a regular service and not as IOption or Configure, because I need to investigate more details about them.

public void ConfigureServices(IServiceCollection services)
{
    // ...

    services.AddSingleton(new BackgroundJobServerOptions());

    services.AddHangfire((provider, config) => config
        .UseSqlServerStorage("Database=WebApplication33; Integrated Security=SSPI;"));

    services.AddHangfireServer();
}

All 11 comments

Yep, that change is nice. Do you know whether Hangfire works with ASP.NET Core 2.0 using the current version of the Hangfire.AspNetCore package?

@odinserj I've just migrated one of my apps to .NET Core 2.0, and it seems to be working fine.

Great, kudos to aspnetcore team for no breaking changes for Hangfire 馃憤

This feature requires a new package, so it should be implemented only if there are significant advantages. Do you know anything about them?

@odinserj not necessarily, you may just add another target for the existing package.
So the netstandard2.0 target uses IHostedService, while the others use the existing implementation.

I don't see any significant benefits of using IHostedService at the moment. I think it is more important to fix the existing implementation first.

In nutshell, the main problem with the current implementation is that it does not know anything about the application's lifecycle.

Let's examine the Program.cs and a Startup class of an aspnet core 2.0 project, it is something like this:

public class Program
{
    public static void Main(string[] args)
    {
        var host = BuildWebHost(args);
        Console.WriteLine("I'm not runnin yet!");
        host.Run();
        Console.WriteLine("Now, i'm running.");
    }

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build();
}

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }
    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
        services.AddSingleton<IHostedService, ExampleHostedService>();
        //services.AddHangfire(config =>
        //        config.UseSqlServerStorage(Configuration.GetConnectionString("HangfireConnection")));
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(
        IApplicationBuilder app, 
        IHostingEnvironment env,
        ILoggerFactory loggerFactory)
    {
        app.UseStaticFiles();
        app.UseMvcWithDefaultRoute();
        //app.UseHangfireServer();
    }
}

The two main function here is

  • ConfigureService: this is where we put things into the IoC container
  • Configure: this is where we assemble the HTTP pipeline

The key here is that without actually calling the IWebHost.Run function, nothing happens, eveything we've done it's just configuration, the ExampleHostedService won't run. The hangfire however will run, tries to connect to DB, tries to run the tasks, etc. This makes a lot of things extremly difficult in case of continuus integration and other tools which are counting on the correct behaviour.

Example: the static "IWebHost BuildWebHost(string[] args)" function is a convention for Entity Framework Tools. This is a tool for tracking changes in the database model, generating migrations, changescripts, etc. The EF Tools will look for this function the create the container (the container is needed to instantiate the dbcontext), but now it also starts the hangfire that leads to unexpected/strange errors.

There are two workarounds currently, but both are very ugly and/or they are forcing us to duplicate a lot of config/code.

  1. For this exact tool, we can implement an IDesignTimeDbContextFactory: the EF tools will use this if provided, but here you need to create the DbContext and all of its dependencies and that is sometimes difficult.
  2. In general, you can make an if statement to put the hangfire out of the pipeline.
//Program.cs
var host = WebHost.CreateDefaultBuilder(args)
    .UseStartup<Startup>()
    .UseSetting("RunHangfire", "false")
    .Build();

//Startup.cs
if(Configuration["RunHangfire"] == "true")
{
    app.UseHangfireServer();
}

With the second option, everything works, but in a long term the Hangfire should also act as expected.

@hurtonypeter intesting solution, but how are we suppose to define "RunHangfire" to true? in the app.settings file? :P

@hurtonypeter intesting solution, but how are we suppose to define "RunHangfire" to true? in the app.settings file? :P

In case of EF Tools which is looking for a "IWebHost BuildWebHost(string[] args)" function, you can simply duplicate the host building.

public class Program
{
    public static void Main(string[] args)
    {
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .UseSetting("RunHangfire", "true")
            .Build()
            .Run();
    }

    // Only used by EF Tooling (migration, etc.)
    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .UseSetting("RunHangfire", "false")
            .Build();
}

But it is offtopic here :)

@odinserj Is there any plan to implement this correctly?

This feature was added in the latest betas of 1.7.0 so you can register Hangfire Server as a hosted service in the .NET Core application as shown below. The current gotcha is you should pass options as a regular service and not as IOption or Configure, because I need to investigate more details about them.

public void ConfigureServices(IServiceCollection services)
{
    // ...

    services.AddSingleton(new BackgroundJobServerOptions());

    services.AddHangfire((provider, config) => config
        .UseSqlServerStorage("Database=WebApplication33; Integrated Security=SSPI;"));

    services.AddHangfireServer();
}

For anyone interested, I created an example showing how to run Hangfire as a stand-alone service whilst hosting the dashboard in a separate web app, here: https://github.com/geirsagberg/HostFun

Was this page helpful?
0 / 5 - 0 ratings