Search code examples
c#dependency-injectionhangfire

Scoped service in Hangfire Job Filter


After searching & reading possibly every discussion related to the topic in question, there seemed not to be a clear (or even unclear) solution to this.

I have a job filter that I want to apply to every job. What I want to do is: when the job fails (goes to FailedState, which happens when the max retry attemps exceed OR it is thrown in that state manually), I want to log a custom, user-friendly exception message to the database (my custom table).

public class WorkflowJobFailureAttribute : JobFilterAttribute, IApplyStateFilter {

    public WorkflowJobFailureAttribute(IServiceProvider serviceProvider) {
        // Getting my required services here
    }

    public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction) 
    {
        var failedState = context.NewState as FailedState;
        if (failedState != null) {
            // - HANDLE FAILED STATE HERE -
                
            // You can get the exception (which is of type `Exception`) using: `failedState.Exception`
        }
    }
}

But every darn example/suggestion I was was about registering the filter globally (GlobalJobFilters.Filters.Add(new WorkflowJobFailureAttribute(serviceProvider)); in Startup.cs). But this is the ROOT service provider, so it will not work with scoped lifetime.

There is a possible 'workaround', suggested by balazs hideghety in the comments here. But before diving deep into that (because it feels like a 'distant' solution), I would like to know: has anyone, in his experience, solved this problem?


Solution

  • Inject a scoped service provider factory and then create the scoped service provider as needed

    public class WorkflowJobFailureAttribute : JobFilterAttribute, IApplyStateFilter {
        private readonly IServiceScopeFactory scopeFactory;
    
        public WorkflowJobFailureAttribute(IServiceScopeFactory scopeFactory) {
            this.scopeFactory = scopeFactory; 
        }
    
        public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction) {
            var failedState = context.NewState as FailedState;
            if (failedState != null) {
                using (IServiceScope scope = scopeFactory.CreateScope()) {
                    IServiceProvider serviceProvider = scope.ServiceProvider;
    
                    // Getting my required services here
    
                    // - HANDLE FAILED STATE HERE -
                    
                    // You can get the exception (which is of type `Exception`) using: `failedState.Exception`
                }
            }
        }
    }
    

    In the preceding code, an explicit scope is created and the service provider can be used to resolve scoped services.