Konrad
Konrad

Reputation: 7208

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.

Upvotes: 0

Views: 4824

Answers (2)

Konrad
Konrad

Reputation: 7208

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.

Upvotes: 4

Steve Harris
Steve Harris

Reputation: 5109

Not sure if this fits your needs, but if both services have the same method (name) you are calling, Make them both implement the same interface so you can inject them into the constructor. Note: You would have to register the services with their interface. Using IEnumerable in the constructor for injecting should give you an instance of all services implementing that interface:

public interface ICronJob
{
    void Run();
}

class Service1 : ICronJob
...

class Service2 : ICronJob
...

public class MyJob
{
    private readonly IEnumerable<ICronJob> _services;

    public MyJob(IEnumerable<ICronJob> services)
    {
        _services = services;
    }

    public void DoSomething() 
    {
        foreach (ICronJob service in services)
            service.Run();
    }

}

Upvotes: 1

Related Questions