Search code examples
c#asp.net-corehangfire

How to send a dbcontext/unit of work to a Hangfire job filter


We're using Hangfire for some nightly and long running jobs, and we're tracking additional related details/metadata for each job within a separate database to avoid Hangfire upgrade issues in the future. A job filter (https://docs.hangfire.io/en/latest/extensibility/using-job-filters.html) would help us track the status of each job in a much easier manner, but I can't find a sample of how to send in dependencies to the job filter.

We're running ASP.NET Core with dependency injection (DI) and the repository + unit of work patterns.

How would I be able to access the database context (or unit-of-work, or any other items available via DI) from within the job filter?

Thank you

EDIT I've created a repository with a small sample project that outlines what I'm trying to do here: https://github.com/joelpereira/hfjobfilter/tree/master/HFJobFilter

It compiles but has an error on the line: .UseFilter(new TypeFilterAttribute(typeof(LogToDbAttribute)))


Solution

  • From the top of my head, you can try adding the JobFilter as a TypeFilter and it will automatically inject the dependencies, if any in the constructor of your LogEverythingAttribute, so modifying the example from the link you provided:

    public class EmailService
    {
        [TypeFilter(typeof(LogEverything))]
        public static void Send() { }
    }
    
    GlobalJobFilters.Filters.Add(new TypeFilterAttribute(typeof(LogEverythingAttribute())));
    

    Disclaimer: I haven't tested the above myself, so please let me know if that works.

    Edited

    Try configuring the Hangfire like below in ConfigureServices and see if that works

    services.AddHangfire(config =>
    {
        config.UseFilter(new TypeFilterAttribute(typeof(LogToDbAttribute)));
    
        // if you are using the sqlserverstorage, uncomment the line and provie
        // the required prameters
        // config.UseSqlServerStorage(connectionString, sqlServerStorageOptions);
    });
    

    UPDATED ANSWER

    Please have a look at the changes I have made to the code you provided. I have tested it and it's working. Few points to note below.

    Please see how I have registered the HttpClient, using the AddHttpClient method that leverages the HttpClientFactory and Typed Clients. That's the recommended way of using HttpClient. You can read more about it here

    services.AddHttpClient<HfHttpClient>(client =>
    {
        client.BaseAddress = new Uri("http://localhost:44303");
    
        // you can set other options for HttpClient as well, such as
        //client.DefaultRequestHeaders;
        //client.Timeout
        //...
    });
    

    In addition, you would need to register the LogDbAttribute and then resolve it in the UseFilter call, using the IServiceProvider

    // register the LogToDbAttribute
    services.AddSingleton<LogToDbAttribute>();
    // build the service provider to inject the dependencies in LogDbAttribute
    var serviceProvider = services.BuildServiceProvider();
    services.AddHangfire(config => config
    .UseSqlServerStorage(Configuration.GetConnectionString("HangfireDBConnection"))
    .UseFilter(serviceProvider.GetRequiredService<LogToDbAttribute>()));
    

    I have also injected the ILogger to demonstrate that it's working. For some reason, if you try to do anything with HttpClient, it hangs. Perhaps, the reason is that it's a background job and all HttpClient calls are asynchronous, so it doesn't come back and two processes trying to wait for each other.

    If you are planning to inject HttpClient, you may need to look into it. However, the logger is working fine.

    Also, you don't need to inherit the LogDbAttribute from TypeFilterAttribute. The TypeFilterAttribute solution doesn't work as I originally suggested.