Singleton service constructed multiple times when using hangfire in asp.net core c#
Asked Answered
S

2

6

I'm using in memory based Hangfire(1.6.21) within an ASP.net core (2.2) application. In Startup.cs I'm configuring a service as a singleton:

services.AddSingleton<IXXXService, XXXService>(); // In ConfigureServices(...)

And also initiating Hangfire with this lines:

app.UseHangfireServer(); // In Configure(...)

Here's the simplified code of XXXService:

public class XXXService : IXXXService
{
    public ExternalAPIService()
    {
        Console.WriteLine("xxx");
    }

    public void QueueRequest(Guid requestId)
    {
        BackgroundJob.Enqueue(() => this.AnalyzeRequest(requestId));
    }

    public async Task AnalyzeRequest(Guid requestId)
    {
        Console.WriteLine("Analyzing request...");
    }
}

The problem is that although XXXService is defined as a singleton - and it really is created only once through consecutive requests, it is recreated by hangfire when eventually calling AnalyzeRequest. How can I route hangfire to use the singleton object managed by the ASP's default DI?

Sorosis answered 19/2, 2019 at 10:6 Comment(7)
I assume Hangfire is using it's own service scope for each background job.Vishinsky
It's a very odd pattern to use dependency injection and call static classes from your code, kinda defeats the idea of an Dependency Injection. Were you meant to use IBackgroundJobClient instead??! Docs clearly state that BackgroundJob is a wrapper around IBackgroundJobClientApogeotropism
@Apogeotropism Not sure what you mean by calling static classes. I have a service declared as singleton to the asp net core service provider. While running, this service needs to start a hangfire job. Not sure how IBackgroundJobClient is relevant here.Sorosis
What makes you actually think its recreated on the callback? Thinking closer about it, the instance should be captured in the lambdas scope. Sure its not some of your code you are omitting causing it to be resolved somewhere?Apogeotropism
@Apogeotropism The constructor is called twice (Console.WriteLine("xxx")) - the second time is after the job is enqueued. The attached code is pretty minimal and reproduces the scenario so I'm not sure what else could be causing this.Sorosis
@MaorVeitsman Did you solve this? I have same problem - hangfire calls constructor every time for recurring tasksOvaritis
@elvis I'm afraid not, I've changed the critical property to be static as a quick solution.Sorosis
R
4

I found I had to do the following.

First we tell the job what service it needs to use. Note here we give it the Interface.

BackgroundJob.Enqueue<IService>(service => service.Method(param));

As suggested we need to add an override to the JobActivator used by Hangfire. Hangfire takes the expression and knows it needs a service that implements IService so we just need to tell it how to get that.

Next we need to create a JobActivator as follows:

public class ServiceProviderActivator : JobActivator
{
    private IServiceProvider _serviceProvider;

    public ServiceProviderActivator(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public override object ActivateJob(Type jobType)
    {
        return _serviceProvider.GetService(jobType);
    }
}

Now when Hangfire tries to run the job it will require an IService and use the service provider. Now all that is left is to give it the same service provider used by your app.

In ConfigureServices in your Startup file:

services.AddHangfire((serviceProvider, configuration) =>
{
    // other configuration
    configuration.UseActivator(new ServiceProviderActivator(serviceProvider));
});
Reign answered 12/2, 2020 at 9:38 Comment(0)
B
1

You can create a JobActivator that uses your IoC to resolve the dependency, So it will always use the singleton class.

Here's an examples: https://docs.hangfire.io/en/latest/background-methods/using-ioc-containers.html

Brief answered 22/11, 2019 at 19:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.