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:

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.