Hangfire icon indicating copy to clipboard operation
Hangfire copied to clipboard

Switching dashboard JobStorage at runtime to support viewing different environments from a single client application

Open dimkaspb opened this issue 8 years ago • 5 comments

We'd like to be able to dynamically switch a JobStorage (SqlServerStorage) in the dashboard at runtime. The idea is to use the same client app (dashboard) to view multiple environments (DEV, QA, PROD) instead of installing a version of the dashboard per environment.

I came across issue #168 that allowed passing a JobStorage instance instead of using JobStorage.Current. The problem I am facing is that JobStorage instance is being passed to the middleware during application startup only, and there seems to be no way to "inject" another instance once middleware had been configured. So the same instance is effectively being used on the dashboard for entire application lifecycle.

My goal would be to build another screen in the client app that is hosting the dashboard, where I can pick an environment (dev, qa, prod). Each environment would have a corresponding connection string that I would use to create a JobStorage. Ideally, once I pick the environment, I would then inject a new JobStorage that can be used by the dashboard, thus allowing the user to switch between environments. I've tried forcing a new JobStorage with GlobalConfiguration.Configuration.UseSqlServerStorage(connectionString); which internally sets JobStorage.Current with new instance but for obvious reasons this did not work, since the middleware had already been configured to use another instance that was passed during initialization.

One way I could see it working is to update the middleware to accept some sort of JobStorageFactory in Hangfire/src/Hangfire.Core/Dashboard/Owin/MiddlewareExtensions.cs to get "current" JobStorage that will be passed to OwinDashboardContext but then why not pass JobStorage.Current instead.

@odinserj Can you suggest a way to achieve what we need?

dimkaspb avatar Apr 04 '17 23:04 dimkaspb

Excuse my questioning, just trying to understand. Is there a reason why you don't want to map a dashboard per environment?

stajs avatar Apr 05 '17 01:04 stajs

We are integrating a dashboard into a test harness web app where it is possible to switch between environments. Mainly, it comes down to less maintenance. Easier to deploy and configure the app in a single location.

dimkaspb avatar Apr 05 '17 04:04 dimkaspb

Same issue here. is there any answer or workaround for that question. That make this product awesome

hafizmoh avatar Jul 23 '18 13:07 hafizmoh

In case someone comes across this and is looking for a solution, here's an altered version of the Hangfire source that achieves this: Hangfire Dashboard JobStorage Factory Gist.

I'm hesitant to create a PR with similar changes. Although it appears to work, I haven't thoroughly reviewed the source to make sure there are no problematic edge cases.

Our use case is a multi-tenant application where we register a dashboard per tenant. Unfortunately due to some limitations in our own codebase, Startup is too early in the process to supply the right JobStorage instance. Using a factory function and copying a bit of the source code was preferable to the larger changes that would have been needed in our own source.

BPotocki avatar Sep 29 '20 14:09 BPotocki

I have a similar scenario as previous commenter with a multi-tenant aspnetcore application and one JobStorage per tenant. The problem is that it is too early to decide what JobStorage (tenant) to get when calling app.UseHangfireDashboard() in Program.cs (Hangfire.Core v1.8.17).

My knowledge of the Hangfire codebase is low. Please squint while reading pseudo solution below. Do you see something like this going into Hangfire.Core?

Program.cs

app.UseHangfireDashboard(storage: null, getStorageEveryTime: true);

HangfireApplicationBuilderExtensions.cs

public static IApplicationBuilder UseHangfireDashboard(
  [NotNull] this IApplicationBuilder app,
  [NotNull] string pathMatch = "/hangfire",
  [CanBeNull] DashboardOptions options = null,
  [CanBeNull] JobStorage storage = null,
              bool getStorageEveryTime = false) // "Get JobStorage from DI every time"
{

...

  var services = app.ApplicationServices;

  storage = storage ?? (getStorageEveryTime ? null : services.GetRequiredService<JobStorage>());

...


  app.Map(new PathString(pathMatch), x => x.UseMiddleware<AspNetCoreDashboardMiddleware>(storage, options, routes));

  return app;
}

AspNetCoreDashboardMiddleware.cs

[CanBeNull] JobStorage storage,
// if (storage == null) throw new ArgumentNullException(nameof(storage));
public async Task Invoke(HttpContext httpContext)
{
  // Every Invoke
  var storage = _storage ?? httpContext.RequestServices.GetRequiredService<JobStorage>();
  var context = new AspNetCoreDashboardContext(storage, _options, httpContext);

...

}

flexdevxyz avatar Dec 19 '24 18:12 flexdevxyz