Search code examples
c#asp.net-mvchangfire

Hangfire - Configure AutomaticRetry for specific RecurringJob at runtime


I'm using Hangfire v1.7.9 and I'm trying to configure a series of recurring background jobs within my MVC 5 application to automate the retrieval of external reference data into the application. I've tested this with one task and this works great, but I'd like administrators within the system to be able to configure the Attempts and DelayInSeconds attribute parameters associated with the method that is called in these background jobs.

The AutomaticRetryAttribute states that you have to use...

...a constant expression, typeof expression or an array creation expression of an attribute parameter type

... which from what I've read is typical of all Attributes. However, this means that I can't achieve my goal by setting a property value elsewhere and then referencing that in the class that contains the method I want to run.

Additionally, it doesn't look like there is any way to configure the automatic retry properties in the BackgroundJob.Enqueue or RecurringJob.AddOrUpdate methods. Lastly, I looked at whether you could utilise a specific retry count for each named Queue but alas the only properties about Hangfire queues you can set is their names in the BackgroundJobServerOptions class when the Hangfire server is initialised.

Have I exhausted every avenue here? The only other thing I can think of is to create my own implementation of the AutomaticRetryAttribute and set the values at compile time by using an int enum, though that in itself would create an issue in the sense that I would need to provide a defined list of each of the values that a user would need to select. Since I wanted the number of retries to be configurable from 5 minutes all the way up to 1440 minutes (24 hours), I really don't want a huge, lumbering enum : int with every available value. Has anyone ever encountered this issue or is this something I should submit as a request on the Hangfire GitHub?


Solution

  • I would take the approach of making a custom attribute that decorates AutomaticRetryAttribute:

    public class MyCustomRetryAttribute : JobFilterAttribute, IElectStateFilter, IApplyStateFilter
    {
        public void OnStateElection(ElectStateContext context)
        {
            GetAutomaticRetryAttribute().OnStateElection(context);
        }
    
        public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
        {
            GetAutomaticRetryAttribute().OnStateApplied(context, transaction);
        }
    
        public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
        {
            GetAutomaticRetryAttribute().OnStateUnapplied(context, transaction);
        }
    
        private AutomaticRetryAttribute GetAutomaticRetryAttribute()
        {
            // Somehow instantiate AutomaticRetryAttribute with dynamically fetched/set `Attempts` value
            return new AutomaticRetryAttribute { Attempts = /**/ };
        }
    }
    

    Edit: To clarify, this method allows you to reuse AutomaticRetryAttribute's logic, without duplicating it. However, if you need to change more aspects on per-job basis, you may need to duplicate the logic inside your own attribute.

    Also, you can use context.GetJobParameter<T> to store arbitrary data on per-job basis