Open KeithBarrows opened 8 years ago
I agreed with @KeithBarrows , especially for queue name. As it's difficult to extend attribute with complex behavior.
It would be nice to add a Nonconcurrent ability to a queue instead of a job. Just a thought I had driving to work today. :)
I agree about this idea: attribute-only configuration/filters means we cannot set configuration at runtime. It also make separation of concerns difficult.
How about a way to add jobParameters at job creation so we could write our own runtime attributes?
Yeah, passing a queue name when enqueuing the job (to be able to set its priority dynamically), as (optional) parameters (or a parameter object) to the enqueue method would be great. Also, for recurring jobs. I currently have exactly that problem.
I also agree with all things mentioned above. We built our application abstracting from Hangfire as much as possible, but the attributes are giving me some headaches. I end up doing strange things using job parameters and global filters rather than just passing the dynamic info I have at runtime to apply to a specific job.
You can define your own implementation of the IJobFilterProvider
interface and register it:
public class MyFilterProvider : IJobFilterProvider
{
public IEnumerable<JobFilter> GetFilters(Job job)
{
if (job.Type.Namespace.StartsWith("ConsoleApp33"))
{
return new[]
{
new JobFilter(new AutomaticRetryAttribute(), JobFilterScope.Global, order: 30),
};
}
return Enumerable.Empty<JobFilter>();
}
}
// Call this startup configuration
JobFilterProviders.Providers.Add(new MyFilterProvider());
I specify queue name via code like so:
var client = new BackgroundJobClient();
var queueName = "myqueu";
var state = new EnqueuedState(queueName);
client.Create<JobClass>(xx => xx.JobMethod(parm), state);
If you need to alter the behaviour of a job at runtime the filters are the way to go (AFAIK). I created a new filter attribute, derived from JobFilterAttribute
like shown in the documentation.
Within each step you can different aspects of the job, but be aware that there are already other job filters already registered within the system, which you like to be run before or after your own filter. Examples are the QueueAttribute
which should normally be run before your own filter or the ContinuationsSupportAttribute
that takes care for awaiting jobs and should normally be run after your own filter. So as a first tip, your constructor should be looking something like this:
public class MyFilterAttribute : JobFilterAttribute, IClientFilter, IServerFilter, IElectStateFilter, IApplyStateFilter
{
private static readonly int DefaultOrder = new ContinuationsSupportAttribute().Order - 1;
public MyFilterAttribute()
{
}
}
Within the OnCreating()
method you can manipulate the InitialState
property of the CreatingContext
. It already contains some state (normally EnqueuedState
or ScheduledState
). In case of the EnqueuedState
you could replace the queue name with whatever you like:
public void OnCreating(CreatingContext context)
{
var enqueuedState = context.InitialState as EnqueuedState;
if (enqueuedState == null)
return;
var (queueName, reason) = DetermineQueue(context.Job);
enqueuedState.Queue = queueName;
enqueuedState.Reason = reason;
}
private (string queueName, string reason) DetermineQueue(Job job)
{
return job.Method.Name.Length % 2 == 0
? ("even", "In even queue cause method name implies it.")
: ("odd", "In odd queue, cause I like so.");
}
`using Hangfire.Common; using Hangfire.States; using IntegraServicos.Domain.Enums; using System; using System.Linq;
namespace IntegraServicos.Helper.HangFire { public class SelectQueueAttribute : JobFilterAttribute, IElectStateFilter {
public void OnStateElection(ElectStateContext context)
{
var enqueuedState = context.CandidateState as EnqueuedState;
if (enqueuedState != null)
{
enqueuedState.Queue = DetermineQueue(context.BackgroundJob.Job);
}
}
String DetermineQueue(Job job)
{
var query = String.Empty;
try
{
var args = job.Args.ToList();
if (args.Count > 1)
{
query = QueueServiceName.Services.Where(x => x.Contains(args[0].ToString().Replace("-", "_").ToLower())).FirstOrDefault().ToLower();
}
else
{
query = QueueServiceName.Services.Where(x => x.Contains(args.Cast<ServiceCallModel>().ToList().FirstOrDefault().ServiceKey.Replace("-", "_").ToLower())).FirstOrDefault().ToLower();
}
}
catch (Exception)
{
Console.WriteLine("Nenhum fila encontrada no argumento, sera enviado para a default");
}
return query ?? "default";
}
}
} `
For instance, we would like to determine this at runtime rather than as an attribute decoration in source code.
We would like to be able to define for a specific job which queue it should run in, whether it is concurrent (in that queue) or can run in parallel and other attributes that we currently have to provide before the job is even compiled.
If this was answered somewhere else, would you point me there please? So far I've not found it if it exists.
TIA