Building upon the answer provided by DotNetAndAngular Developer, I created a custom RetryAttempts
attribute that allows you to specify the ordinal position of a retryAttempts
method parameter, which matches the behavior of the existing Queue
attribute and DisplayName
attributes.
This custom RetryAttempts
attribute allows you to dynamically specify the retry attempts for a given job at runtime.
Here is an example of using the custom RetryAttempts
attribute along with using the aforementioned Queue
attribute and DisplayName
attribute:
var jobId = BackgroundJob.Enqueue(
() => ProcessRequest("Default", "Process Pending Orders", 3, request));
// ...
/// <summary>
/// Processes the request.
/// </summary>
/// <param name="queueName">Queue to assign the job to. The Queue name should only include lowercase letters, digits, underscore, and hyphen characters.</param>
/// <param name="jobName">Job name.</param>
/// <param name="retryAttempts">Maximum retry attempts.</param>
/// <param name="request">Request to execute.</param>
[Queue("{0}")] // The first method parameter is the queue name
[DisplayName("{1}")] // The second method parameter is the job name
[RetryAttempts("{2}")] // The third method parameter is the max retry attempts
public async Task ProcessRequest(
#pragma warning disable IDE0060 // Remove unused parameter
string queueName,
string jobName,
int retryAttempts,
#pragma warning restore IDE0060 // Remove unused parameter
object request)
{
// Process the thing
}
Here is the code for the RetryAttempts
attribute:
using Hangfire;
using Hangfire.Common;
using Hangfire.States;
using Hangfire.Storage;
using System.Globalization;
namespace HangfireHelpers;
/// <summary>
/// Custom Hangfire attribute that allows dynamically setting the retry attempts for a job
/// by specifying the positional method parameter that defines the retry attempts.
/// </summary>
/// <remarks>
/// For example, if the retry attempts is defined as the 2nd positional method parameter (zero-indexing),
/// then you would decorate the method with the following attribute: [RetryAttempts("{2}")]
/// </remarks>
public class RetryAttemptsAttribute : JobFilterAttribute, IElectStateFilter, IApplyStateFilter
{
private AutomaticRetryAttribute? _baseAttribute;
private readonly string _retryAttempsParameterPosition;
const string ValidationErrorMessage = "Invalid RetryAttemptsAttribute parameter. Example of valid value: {3}";
public RetryAttemptsAttribute(string retryAttempsParameterPosition)
{
_retryAttempsParameterPosition = retryAttempsParameterPosition;
}
public void OnStateElection(ElectStateContext context)
{
var retryAttemptsValue = string.Format(CultureInfo.InvariantCulture, _retryAttempsParameterPosition, context.BackgroundJob.Job.Args.ToArray());
if (!int.TryParse(retryAttemptsValue, out var retryAttempts))
{
throw new Exception(ValidationErrorMessage);
}
int retryCount = context.GetJobParameter<int>("RetryCount");
if (retryCount >= retryAttempts)
{
context.CandidateState = new global::Hangfire.States.DeletedState();
}
GetAutomaticRetryAttribute(retryAttempts).OnStateElection(context);
}
public void OnStateApplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
var retryAttemptsValue = string.Format(CultureInfo.InvariantCulture, _retryAttempsParameterPosition, context.BackgroundJob.Job.Args.ToArray());
if (!int.TryParse(retryAttemptsValue, out var retryAttempts))
{
throw new Exception(ValidationErrorMessage);
}
GetAutomaticRetryAttribute(retryAttempts).OnStateApplied(context, transaction);
}
public void OnStateUnapplied(ApplyStateContext context, IWriteOnlyTransaction transaction)
{
var retryAttemptsValue = string.Format(CultureInfo.InvariantCulture, _retryAttempsParameterPosition, context.BackgroundJob.Job.Args.ToArray());
if (!int.TryParse(retryAttemptsValue, out var retryAttempts))
{
throw new Exception(ValidationErrorMessage);
}
GetAutomaticRetryAttribute(retryAttempts).OnStateUnapplied(context, transaction);
}
private AutomaticRetryAttribute GetAutomaticRetryAttribute(int retryAttempts)
{
_baseAttribute = new AutomaticRetryAttribute { Attempts = retryAttempts };
return _baseAttribute;
}
}
context.GetJobParameter<T>
method I can retrieve the configuration model object from the Job and then implement the number of retries from there :) – Shakitashako