Search code examples
c#annotationshangfire

Can one create abstractions for annotations?


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?


Solution

  • 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());