Hangfire icon indicating copy to clipboard operation
Hangfire copied to clipboard

IPerformContextAccessor like IHttpContextAccessor

Open domagojmedo opened this issue 3 years ago • 2 comments

Is it possible to have something like IHttpContextAccessor in ASP.NET (let's call it IPerformContextAccessor ) where we would be able to inject it to get current job execution context?

For example we have IUser interface that resolves by injecting IHttpContextAccessor and reading claims for current user. For hangfire scenario we would like to do the same but instead of reading claims from HttpContext we would like to access job information.

Is this possible to achieve?

domagojmedo avatar May 31 '22 10:05 domagojmedo

using Hangfire;

var host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((context, services) =>
    {
        services.AddTransient(x => new AutomaticRetryAttribute
        {
            Attempts = 0
        });

        services.AddScoped<IUser, User>();
        services.AddScoped<IHangfireContext, HangfireContext>();
        services.AddTransient<CustomJobActivator>();

        services.AddHangfire((provider, configuration) =>
        {

            configuration.UseActivator(provider.GetService<CustomJobActivator>()!);
            configuration.UseSqlServerStorage("server=.;database=Hangfire;trusted_connection=True;");
            configuration.UseFilter(provider.GetRequiredService<AutomaticRetryAttribute>());
        });

        services.AddHangfireServer((provider, configuration) => { });
    })
    .Build();

var background = host.Services.GetService<IBackgroundJobClient>();
background.Enqueue<TestJob>(x => x.Run());

await host.RunAsync();

public class TestJob
{
    private readonly ILogger<TestJob> _logger;
    private readonly IUser _user;

    public TestJob(ILogger<TestJob> logger, IUser user)
    {
        _logger = logger;
        _user = user;
    }

    public void Run()
    {
        _logger.LogInformation("Job run for id {job} job name {name}", _user.Id, _user.Name);
    }
}

public interface IUser
{
    public string Id { get; }
    public string Name { get; }
}

public class User : IUser
{
    public User(IHangfireContext hangfireContext)
    {
        Id = hangfireContext.PerformContext?.BackgroundJob.Id ?? "-";
        Name = hangfireContext.PerformContext?.BackgroundJob.Job.Type.Name ?? "-";
    }

    public string Id { get; }

    public string Name { get; }
}

public class CustomJobActivator : JobActivator
{
    private readonly IServiceProvider _serviceProvider;

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

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

    public override JobActivatorScope BeginScope(JobActivatorContext context)
    {
        return new CustomScope(_serviceProvider.CreateScope(), context);
    }
}

public class CustomScope : JobActivatorScope
{
    private readonly IServiceScope _serviceScope;

    public CustomScope(IServiceScope serviceScope, JobActivatorContext context)
    {
        _serviceScope = serviceScope;

        var hangfireContext = _serviceScope.ServiceProvider.GetRequiredService<IHangfireContext>();
        hangfireContext.PerformContext = context;
    }

    public override object Resolve(Type type)
    {
        return ActivatorUtilities.CreateInstance(_serviceScope.ServiceProvider, type);
    }

    public override void DisposeScope()
    {
        _serviceScope.Dispose();
    }
}

public class HangfireContext : IHangfireContext
{
    public JobActivatorContext? PerformContext { get; set; }
}

public interface IHangfireContext
{
    JobActivatorContext? PerformContext { get; set; }
}

Managed to get something like this to work, not sure if there's a better way

domagojmedo avatar May 31 '22 12:05 domagojmedo

Here is my library that uses AsyncLocal to get current Job PerformContext (same as HttpContextAccessor for HTTP request flow)

See: https://github.com/meriturva/Hangfire.PerformContextAccessor

meriturva avatar Sep 30 '22 21:09 meriturva