Search code examples
asp.net-coredependency-injectionhangfire

How to use Hangfire with dependency injection in ASP.NET Core?


I would like to run a recurring job which has previously registered in the IoC in ASP.NET Core 8.

Something like this:

  • I register a class in the IoC
  • The framework takes care of all the dependencies that my class needs.
  • Then, I would like to use Hangfire to run a method from that registered class with all the dependencies resolved by the framework.

Basically I have this configuration for Hangfire.

builder.Services.AddHangfire(config => config
                       .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
                       .UseSimpleAssemblyNameTypeSerializer()
                       .UseDefaultTypeSerializer()
                       .UseInMemoryStorage());

builder.Services.AddHangfireServer();

After this I have the following MappingStoringGroup class registered in the IoC as follow:

builder.Services.AddKeyedScoped<ITask, MappingTask>(nameof(MappingTask));
builder.Services.AddKeyedScoped<ITask, StoringTask>(nameof(StoringTask));

builder.Services.AddScoped<IRepository, SqlRepository>();

    builder.Services.AddKeyedScoped<ITaskGroup>(nameof(MappingStoringGroup), (provider, key) =>
    {
        var taskGroup = new MappingStoringGroupBuilder()
                        .AddTask(provider.GetRequiredKeyedService<ITask>(nameof(MappingTask)))
                        .AddTask(provider.GetRequiredKeyedService<ITask>(nameof(StoringTask)))
                        .CreateTaskGroup();

        return taskGroup;
    });

What I am looking after would something like this:

app.UseHangfireDashboard();

RecurringJob.AddOrUpdate<ITaskGroup>("job id", nameof(MappingStoringGroup), tg => tg.RunTasks(), Cron.Hourly);

app.Run();

There are two reasons why I have to register MappingStoringGroup like this in the IoC. First, all the task classes added to MappingStoringGroup have different dependencies in the constructor. Second, MappingStoringGroup.RunTasks() will be also called via controller, executing the tasks manually.

I tried to use Hangfire during the registration of MappingStoringGroup.

For instance:

builder.Services.AddKeyedScoped<ITaskGroup>(nameof(MappingStoringGroup), (provider, key) =>
    {
        var taskGroup = new MappingStoringGroupBuilder()
                        .AddTask(provider.GetRequiredKeyedService<ITask>(nameof(MappingTask)))
                        .AddTask(provider.GetRequiredKeyedService<ITask>(nameof(StoringTask)))
                        .CreateTaskGroup();
      
        RecurringJob.AddOrUpdate("job id", () => taskGroup.RunTasks(), Cron.Hourly);

        return taskGroup;
    });

But it did not work. The Hangfire dashboard will show zero recurring jobs.


Solution

  • As commented in the thread, the solution is to create a custom activator:

        public class HangfireCustomActivator(IServiceScopeFactory container) : JobActivator
        {
            public override object ActivateJob(Type type)
            {
                using var scope = container.CreateScope();
                return scope.ServiceProvider.GetRequiredKeyedService<ITaskGroup>(type.Name);
            }
        }
    

    And then use it in the Hangfire configuration.

        builder.Services.AddHangfire((provider, config) => config
            .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
            .UseSimpleAssemblyNameTypeSerializer()
            .UseDefaultTypeSerializer()
            .UseInMemoryStorage()
            .UseActivator(new HangfireCustomActivator(provider.GetRequiredService<IServiceScopeFactory>())));
    
        builder.Services.AddHangfireServer();
    

    This works.