Search code examples
c#asp.net-corehangfire

Add recurring job that uses multiple services


So, Hangfire provides IRecurringJobManager we can use to add recurring jobs but it only supports injecting one service and I couldn't find any example of how to do it when I need to access multiple services.

Consider I have two registered services like this:

services.AddTransient<Service1>();
services.AddTransient<Service2>();

And I would like to use them both in my recurring job. Ideally what I would like to do is something like:

recurringJobManager.AddOrUpdate<Service1, Service2>("my-job", (service1, service2) =>
{
    // do something
}, Cron.Daily);

But it's not possible.

What is the correct way to do this? Should I inject IServiceProvider? Like this?

recurringJobManager.AddOrUpdate<IServiceProvider>("my-job", (serviceProvider) =>
{
    var service1 = serviceProvider.GetRequiredService<Service1>();
    var service2 = serviceProvider.GetRequiredService<Service2>();
    // do something
}, Cron.Daily);

Will that work? EDIT: no, because I cannot use statement bodies inside

I'm kinda confused because documentation doesn't cover these cases.


Solution

  • One solution seems to be to create a separate class with services I need

    public class MyJob
    {
         private readonly Service1 _service1;
         private readonly Service2 _service2;
    
         public MyJob(Service1 service1, Service2 service2)
         {
             _service1 = service1;
             _service2 = service2;
         }
    
         public void DoSomething() {}
    }
    

    And then use it like:

    recurringJobManager.AddOrUpdate<MyJob>("my-job", myJob => myJob.DoSomething(), Cron.Daily);
    

    Note: we cannot use statement body {} with AddOrUpdate. It always needs to be a single method call as it takes Expression<T> as a parameter. So if we try to do recurringJobManager.AddOrUpdate<MyJob>("my-job", myJob => {myJob.DoSomething(); myJob.DoSomething2();}, Cron.Daily); we will get compiler error.

    Another solution is a separate method that takes IServiceProvider as a parameter. But I can't guarantee it works.

    // Or just use MyJob without any parameters if it has access to Service1 and Service2 in case they're members of your class.
    private void MyJob(IServiceProvider services)
    {
        var service1 = services.GetRequiredService<Service1>();
        var service2 = services.GetRequiredService<Service2>();
    
        // do something
    }
    
    // Add like this or just inject `IServiceProvider` somewhere else e.g. in Configure method
    recurringJobManager.AddOrUpdate<IServiceProvider>("my-job", services => MyJob(services), Cron.Daily);
    

    Also check https://docs.hangfire.io/en/latest/best-practices.html#best-practices

    So ideally you shouldn't use IServiceProvider as job argument because it will be serialized.

    ⚠️ Important thing to notice that got me extremely confused at first!

    when you do

    var myJob = new MyJob();
    RecurringJob.AddOrUpdate("my-job", () => myJob.Run(), Cron.Hourly(0));
    

    It won't use that instance you created but create a new one every time. So the above code is 100% equivalent with

    RecurringJob.AddOrUpdate<MyJob>("my-job", (myJob) => myJob.Run(), Cron.Hourly(0));
    

    Because what AddOrUpdate does internally is it parses expression, determines type of myJob and the method called and constructs a new expression to create instance of that type and call that method on a new instance.