Hangfire Discussion

Changing hangfire connection at runtime


#1

Hi, i’m trying to use hangfire in our multi-tenant application, which has 2 databases, but sharing the same application code.

My goal is, depending on the URL (tenant1 ot tenant2), i need to create the job in the correct database. Tenant1 client goes to tenant1 DB, Tenant2 client goes to Tenant2 DB.

But it seems that after i have setup the connection string for the first time, i can’t change it for the subsequent time.

For example, this code :

Line1 GlobalConfiguration.Configuration.UseSqlServerStorage(tenant1DBConnectionString);

Line2 BackgroundJob.Enqueue(JOB1) --> queue inserted to tenant1DB

Line3 GlobalConfiguration.Configuration.UseSqlServerStorage(tenant2DBConnectionString);

Line4 BackgroundJob.Enqueue(JOB2) --> queue inserted to tenant2DB instead of tenant1DB, which is wrong

Is there a way to change the DB Connection string to be used properly?


#2

Don’t use static methods like BackgroundJob.Enqueue(). Create a new instance of BackgroundJobClient every time, passing it a corresponding instance of SqlServerStorage as argument.

If you use MVC and IoC, you may configure separate Areas, each having its own JobStorage and IBackgroundJobClient services configured, so you can seamlessly inject them.

You may even turn these services into Scoped, so they’re resolved for each request. For .NET Core, it would be something like:

// Needed for JobStorage factory to work
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

services.AddScoped<JobStorage>(x =>
{
    var httpContext = x.GetRequiredService<IHttpContextAccessor>().HttpContext;
    if (httpContext == null)
    {
        // If we don't have HttpContext present, then it must be UseHangfireServer/Dashboard()
        // was called without the JobStorage parameter specified, so it tries to resolve it from IoC.
        throw new InvalidOperationException("Server/dashboard must be started with JobStorage specified");
    }
    
    // inspect httpContext.User or httpContext.Request to determine the connection string
    return new SqlServerStorage(GetConnectionString(httpContext));
});

// Services depending on JobStorage must be changed to Scoped too:

services.AddScoped<IBackgroundJobClient>(x => new BackgroundJobClient(
    x.GetRequiredService<JobStorage>(),
    x.GetRequiredService<IBackgroundJobFactory>(),
    x.GetRequiredService<IBackgroundJobStateChanger>()));

services.AddScoped<IRecurringJobManager>(x => new RecurringJobManager(
    x.GetRequiredService<JobStorage>(),
    x.GetRequiredService<IBackgroundJobFactory>()));

// Proceed with Hangfire setup

services.UseHangfire(configuration => 
{
    // Don't call UseSqlServerStorage() here
    // ...
});

#3

this is spot on! this is what i was looking for, to instantiate the BackgroundJobClient in every request, and use it instead the static BackgroundJob.Enqueue().

I don’t use .NET Core, i’m using servicestack for the app service. So for now i’m just gonna try the traditional way using Property in the service class and return the BackgroundJobClient with the connectionstring depending on the request.

But, the samples you gave me is very fancy, will do some R n D on that! Thank you!


#5

I have used this for my multi-tenant project, mostly similar scenario. i have used RecurringJobManager instance in the middle ware and dynamically change sqlserverstorage as below

            GlobalConfiguration.Configuration.UseSqlServerStorage(tenant.ConnectionString);
            GlobalConfiguration.Configuration.UseStorage(new SqlServerStorage(tenant.ConnectionString));
            recurringJobManager.AddOrUpdate(tenant.Name, Job.FromExpression(() => Console.WriteLine("Great")), Cron.MinuteInterval(1));

but, another problem, that jobs record only in default tenant, it is not using another tenant to save its own jobs info