0

I've created following abstraction for scheduling jobs:

public interface IJobData
{ }

public interface IJob<in TJobData> where TJobData : IJobData
{
    Task ExecuteAsync(TJobData jobData);
}

I use this in the application layer to create jobs. E.g.

public record ForgotPasswordJobData() : IJobData;

public class ForgotPasswordJob : IJob<ForgotPasswordJobData>
{
    public Task ExecuteAsync(ForgotPasswordJobData jobData)
    {
        // Do some work
    
        return Task.CompletedTask; 
    }
}

Now I want to decorate the ExecuteAsync method with

[AutomaticRetry(Attempts = 5)]

However I dont want to put it in the application layer, because this will create a dependency on the infrastructure layer. AutomaticRetry is a feature of the hangfire library, which sits in the infrastructure layer.

Is there a way to abstract [AutomaticRetry(Attempts = 5)] in the application layer?

DarkLeafyGreen
  • 69,338
  • 131
  • 383
  • 601
  • 1
    I would go for JobFilter with passive attribute. See https://stackoverflow.com/a/57396553/1236044 In the ServerFilterProvider.GetFilters method, you should "just" need to search the job.Method.GetCustomAttributes for your own passive `MyAutomaticRetry` attribute. Depending if you find one or not, you would return a JobFilter holding a Hangfire.AutomaticRetryAttribute. Sorry miss time to set up a correct answer – jbl Dec 10 '21 at 16:51

2 Answers2

1

This comment got me on the right track:

I would go for JobFilter with passive attribute. See stackoverflow.com/a/57396553/1236044 In the ServerFilterProvider.GetFilters method, you should "just" need to search the job.Method.GetCustomAttributes for your own passive MyAutomaticRetry attribute. Depending if you find one or not, you would return a JobFilter holding a Hangfire.AutomaticRetryAttribute. Sorry miss time to set up a correct answer

Define custom attribute in application layer:

[AttributeUsage(AttributeTargets.Method)]
public class JobRetryAttribute : Attribute
{
    public int Attempts;

    public bool DeleteOnAttemptsExceeded;

    public JobRetryAttribute(int Attempts, bool DeleteOnAttemptsExceeded)
    {
        this.Attempts = Attempts;
        this.DeleteOnAttemptsExceeded = DeleteOnAttemptsExceeded;
    }
}

Create hangfire job filter provider:

public class ServerFilterProvider : IJobFilterProvider
{
    public IEnumerable<JobFilter> GetFilters(Job job)
    {
        var attribute = job.Method
            .GetCustomAttributesData()
            .Where(a => a.AttributeType == typeof(JobRetryAttribute))
            .Select(a => new AutomaticRetryAttribute
            {
                Attempts = Convert.ToInt32(a.ConstructorArguments.First().Value),
                OnAttemptsExceeded = Convert.ToBoolean(a.ConstructorArguments.Skip(1).First().Value) ? AttemptsExceededAction.Delete : AttemptsExceededAction.Fail
            })
            .First();

        if (attribute != null)
        {
            return new JobFilter[] { new JobFilter(attribute, JobFilterScope.Method, null) };
        }

        return Enumerable.Empty<JobFilter>();
    }
}

Use it:

JobFilterProviders.Providers.Add(new ServerFilterProvider());
DarkLeafyGreen
  • 69,338
  • 131
  • 383
  • 601
0

If you use Interface the attribute will not inherited to your implementation.

but if you use abstract class it will inherit

public interface IJobData
{ }

public abstract class MyJob<TJobData> where TJobData : IJobData
{
    [AutomaticRetry(Attempts = 5)]
    public abstract Task ExecuteAsync(TJobData jobData);
}
Asakuraa Ranger
  • 577
  • 7
  • 18