I have created a generic job schedule manager class. I am using Quartz.net 3.0.7 in .net core app.
public class ScheduleManagerManager<TJob> : IScheduleManager where TJob : IJob
{
private readonly string _cronExpression;
private readonly string _jobIdentity;
private readonly string _triggerIdentity;
private IScheduler _scheduler;
private IJobDetail _jobDetail;
private ITrigger _trigger;
public ScheduleManagerManager(string cronExpression)
{
_cronExpression = cronExpression;
_jobIdentity = Guid.NewGuid().ToString();
_triggerIdentity = Guid.NewGuid().ToString();
}
public async Task Run()
{
try
{
_scheduler = await StdSchedulerFactory.GetDefaultScheduler();
await _scheduler.Start();
_jobDetail = JobBuilder.Create<TJob>()
.WithIdentity(_jobIdentity)
.Build();
_trigger = TriggerBuilder.Create()
.WithIdentity(_triggerIdentity)
.StartNow()
.WithCronSchedule(_cronExpression)
.Build();
await _scheduler.ScheduleJob(_jobDetail, _trigger);
}
catch (SchedulerException se)
{
await Console.Out.WriteLineAsync(se.Message);
}
}
public void ShutDown()
{
_scheduler.Shutdown();
}
}
So I amusing this in applications like following:
var manager1 = new ScheduleManagerManager<MailSender>();
var manager2 = new ScheduleManagerManager<SmsSender>();
I want to add an event handler to get job data after executed the job.
var manager1 = new ScheduleManagerManager<MailSender>();
manager1.JobExecuted += OnJobExecuted();
But there is no event in Quartz.net jobs or triggers. How can I do it?
But there is no event in Quartz.net jobs or triggers. Of course they are, they are called Listener.
Just implement IJobListener:
public class MyJobListener : IJobListener
{
public string Name () => "MyListener";
public Task JobToBeExecuted(IJobExecutionContext context){ }
public Task JobExecutionVetoed(IJobExecutionContext context){ }
public Task JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException)
{
// Do after Job stuff....
}
}
And add it to your scheduler in your ScheduleManagerManager:
var myJobListener = new MyJobListener();
_scheduler.ListenerManager.AddJobListener(myJobListener, GroupMatcher<JobKey>.AnyGroup());
Related
I want to register consumer by interface, send message, initialize it by interface from container, then consume:
public sealed class TestConsumer<T> : IConsumer<T>
where T : class
{
private readonly Func<ConsumeContext<T>, Task> _onConsume;
private readonly EventWaitHandle _handle;
public TestConsumer(Func<ConsumeContext<T>, Task> onConsume)
{
_onConsume = onConsume;
_handle = new EventWaitHandle(false, EventResetMode.ManualReset);
}
public async Task Consume(ConsumeContext<T> context)
{
try
{
await _onConsume(context).ConfigureAwait(false);
}
finally
{
_handle.Set();
}
}
public async Task GetTask()
{
while (!_handle.WaitOne(0))
await Task.Delay(100);
}
}
public class MyRequest { }
[TestFixture]
public class ConsumerTests
{
[Test]
public async Task Test()
{
var services = new ServiceCollection();
var tc = new TestConsumer<MyRequest>(async (c) => Console.WriteLine("request"));
services.AddSingleton<IConsumer<MyRequest>>(tc);
services.AddSingleton<IBusControl>(x => Bus.Factory.CreateUsingInMemory(cfg =>
{
cfg.ReceiveEndpoint("foobar", c => { c.Consumer<IConsumer<MyRequest>>(x); });
}));
var sp = services.BuildServiceProvider();
await sp.GetRequiredService<IBusControl>().StartAsync();
//and how do I send it?
//this will obviously not work with Uri!!!
var sendEndpoint = await sp.GetRequiredService<IBusControl>().GetSendEndpoint(new Uri("foobar", UriKind.Relative));
await sendEndpoint.Send(new MyRequest());
await tc.GetTask();
Console.WriteLine("done");
}
}
Honestly, lack of documentation is driving me crazy. There is such thing as harness, but it works only if you throw your DI container into garbage can or write a ton of adapters.
How do one can use InMemory and combine it to completely uncompatible Uri in Send method?
I'm trying to create trigger and start job using quartz.net 3.0 in my net core app.
Here is a code :
mycontroller.cs
public async Task<IActionResult> CreateHomeWorkReminder([FromBody] CreateHomeWorkReminderRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
_logger.LogInformation("data valid");
var dict = new Dictionary<string, object> { {nameof(CreateHomeWorkReminderRequest.UserId), request.UserId },
{nameof(CreateHomeWorkReminderRequest.HomeWorkId), request.HomeWorkId}};
await _registerJob.CreateJob<ReminderJob>(nameof(CreateHomeWorkReminder), dict, request.DateOfReminder);
_logger.LogInformation("saving ok");
return Ok(result.Result);
}
RegisterJob.cs (DI in contructor in mycontroller.cs) :
public class RegisterJob : IRegisterJob
{
ISchedulerBot bot;
public async Task CreateJob<T>(string jobName, IDictionary<string, object> map, DateTime dateOfReminder) where T : IJob
{
NameValueCollection props = new NameValueCollection
{
{ "quartz.serializer.type", "binary" }
};
StdSchedulerFactory factory = new StdSchedulerFactory(props);
// get a scheduler
IScheduler sched = await factory.GetScheduler();
await sched.Start();
var job = JobBuilder.Create<ReminderJob>().Build();
var trigger = TriggerBuilder.Create().StartNow().Build();
await sched.ScheduleJob(job, trigger);
}
}
}
and ReminderJob.cs
public class ReminderJob : IJob
{
ILogger<ReminderJob> logger;
public ReminderJob(ILogger<ReminderJob> _logger)
{
logger = _logger;
}
public Task Execute(IJobExecutionContext context)
{
var parameters = context.JobDetail.JobDataMap;
var userId = parameters.GetLongValue("UserId");
var homeWorkId = parameters.GetLongValue("HomeWorkId");
logger.LogInformation($"userId = {userId}, homeWorkId = {homeWorkId}");
return Task.FromResult(0);
}
}
I'm trying to debug my code, but never go to the Execute method of ReminderJob.cs. Where is my mistake? What should I to execute job? Thank you for your attention.
Right answer : it's impossible to use DI with Quartz 3.0. I deleted constructor in ReminderJob with DI of ILogger and code worked.
public class ReminderJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
var parameters = context.JobDetail.JobDataMap;
var userId = parameters.GetLongValue("UserId");
var homeWorkId = parameters.GetLongValue("HomeWorkId");
await System.Console.Out.WriteLineAsync("HelloJob is executing.");
}
I'm using newest 3.0.6 version of Quartz.net
I want o schedule a process to run each day (currently in example it's every 40seconds but I will change that)
I have method where I call it to execute, however it does not seem to start any process which it should as I initiated it in `Job' class. I'm not sure why it's not trigerring?
public static void Run()
{
var isRunning = false;
IServiceCollection services = new ServiceCollection();
Startup startup = new Startup();
var serviceProvider = startup.ConfigureServices(services);
IWindsorContainer _container = serviceProvider.GetService<IWindsorContainer>();
while (true)
{
if (!isRunning)
{
isRunning = true;
var configuration = _container.Resolve<IConfigurationRoot>();
var _process = _container.Resolve<Process.Process>();
int secondsForSleep = Convert.ToUInt16(configuration[Enums.SleepTime]);
{
try
{
execute();
}
catch (Exception ex)
{
}
finally
{
isRunning = false;
Thread.Sleep(secondsForSleep);
_container.Release(_process);
}
}
}
}
}
public static async void execute()
{
// construct a scheduler factory
NameValueCollection props = new NameValueCollection
{
{ "quartz.serializer.type", "binary" }
};
StdSchedulerFactory factory = new StdSchedulerFactory(props);
// get a scheduler
IScheduler sched = await factory.GetScheduler();
await sched.Start();
IJobDetail job = JobBuilder.Create<Job>()
.WithIdentity("myJob", "group1")
.Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("myTrigger", "group1")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(1)
.RepeatForever())
.Build();
await sched.ScheduleJob(job, trigger);
}
}
public class Job : IJob
{
public async Task Execute(IJobExecutionContext context)
{
JobKey key = context.JobDetail.Key;
IServiceCollection services = new ServiceCollection();
Startup startup = new Startup();
var serviceProvider = startup.ConfigureServices(services);
IWindsorContainer _container = serviceProvider.GetService<IWindsorContainer>();
var configuration = _container.Resolve<IConfigurationRoot>();
var _process = _container.Resolve<Process.Process>();
Task t = new Task(() =>
{
_process.MainProcess();
});
t.Start();
await t;
}
}
Do I incorrectly set IJobDetail object or something? does anyone had a similar problem? Seems like i did eveerything according to documentation but I still can't get it working
This is not directly the answer to your question but may help you if the problem is related to below.
Look, you have defined the execute method as async void. As a result, you cannot call it via await execute() and so
your try-catch block will not handle any exceptions raised in the execute method
finally block runs directly after calling execute() method (not waiting for method finishing)
In other words, change your code to async Task execute() and await execute() and test it again.
For more theory I suggest you read this SO: async/await - when to return a Task vs void? and msdn: Async/Await - Best Practices
I have two action method DayStart() and DayEnd() and I have to call DayStart()
at 8:05 am (Mon-Fri) and DayEnd() at 8:04 am (Mon-Fri) and for this the best option so far in Quartz.net but I don't know how do I call these two action method in jobExecution method.
Is there any way to achieve this?
public ActionResult StartDay()
{
int userid = Auth.UserID;
daysServices.StartDay(userid);
return RedirectToAction("Index", "Home", new { isdayClose = false });
}
public ActionResult CloseDay()
{
int userid = Auth.UserID;
int dayId = daysServices.getActiveDay();
int shiftId = daysServices.getActiveShift();
daysServices.CLoseDay(dayId, userid, shiftId);
return RedirectToAction("TokenDayAmount", "Home", new { isdayClose = true });
}
public async Task Execute(IJobExecutionContext context)
{
await Console.Out.WriteLineAsync("HelloJob is executing.");
//I hope something like this? DayStart();
}
You need three steps for this. First create a job
public class SomeJob : IJob
{
public void Execute(IJobExecutionContext context)
{
DayStart(); //this is your start method
}
}
Second you need a scheduler when your job will execute. You can use Cron Trigger to schedule your job like
public class SomeJobScheduler
{
public static void Start()
{
IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
scheduler.Start();
IJobDetail job = JobBuilder.Create<SomeJob>().Build();
ITrigger trigger = TriggerBuilder.Create()
.WithCronSchedule("0 05 8 ? * MON-FRI *") //This expression to schedule your job Mon-Fri 8.05 AM
.Build();
scheduler.ScheduleJob(job, trigger);
}
}
You can find more about Cron Ttrigger Here
Third call your Scheduler in Global,asax.cs.
void Application_Start(object sender, EventArgs e)
{
SomeJobScheduler.Start();
}
I have different classes as descendants of a base class (Worker).
Each of these classes has it's own quartz job and trigger and a
callback.
public class Worker1 : Worker, ICallback
{
IScheduler scheduler;
public Worker1(IScheduler scheduler)
: base("Worker1")
{
this.scheduler = scheduler;
IJobDetail job = JobBuilder.Create<MonitorJob>()
.WithIdentity(name + "Job")
.Build();
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity(name + "Trigger")
.StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(1)
.RepeatForever())
.Build();
scheduler.ScheduleJob(job, trigger);
}
public void Callback()
{
Console.WriteLine(name + " callback " + DateTime.Now.ToLongTimeString());
}
}
Now I want on trigger of job1 (from worker1) a callback to worker1.callback.
Same with job2 (from worker2) a callback to worker2.callback.
With autofac I am able to inject a callback into my job - however I can only inject a common callback, not as I like one for each class.
public class MonitorJob : IJob
{
ICallback callback;
public MonitorJob(ICallback callback)
{
this.callback = callback;
}
public void Execute(IJobExecutionContext context)
{
callback.Callback();
}
}
my main class creates the autofac container
container = ConfigureContainer(new ContainerBuilder()).Build();
using (container.BeginLifetimeScope())
{
workers.Add(container.Resolve<Worker1>());
workers.Add(container.Resolve<Worker2>());
}
var factory = container.Resolve<ISchedulerFactory>();
var scheduler = factory.GetScheduler();
scheduler.Start();
internal static ContainerBuilder ConfigureContainer(ContainerBuilder cb)
{
cb.RegisterModule(new QuartzAutofacFactoryModule());
cb.RegisterModule(new QuartzAutofacJobsModule(typeof(QuartzScheduler.MonitorJob).Assembly));
cb.RegisterType<Worker1>().AsSelf();
cb.RegisterType<Worker2>().AsSelf();
return cb;
}
Autofac creates the job and also could inject it with a callback, however I want the right callback from the worker class that "owns" the job.
How would this be possible?
Otherwise I would need to somehow propagate from one common callback to the respective worker class.
Thank you in advance
I succeeded by creating a generic job:
public class MonitorJob<T> : IJob where T:IWorker
{
T worker;
public MonitorJob(T worker)
{
this.worker = worker;
}
public void Execute(IJobExecutionContext context)
{
worker.Callback();
}
}
In my custom worker class I create the monitor job for this worker:
public Worker1(IScheduler scheduler)
{
this.scheduler = scheduler;
IJobDetail job = JobBuilder.Create<MonitorJob<Worker1>>()
.WithIdentity(name + "Job")
.Build();
[...]
}
and so the right callback class will be resolved by:
container.RegisterType<Worker1>().AsSelf().SingleInstance();
container.RegisterType<MonitorJob<Worker1>>().AsSelf();
[...]
workers.Add(scope.Resolve<Worker1>());
so always the right callback is executed for each individual job.
Autofac has no way to know how to bind ICallback and IJob. How would like to bind them ?
The simplest solution is to introduce a dependency between these 2 classes
public class MonitorJob : IJob
{
public MonitorJob(Worker1 worker1)
{
this._worker1 = worker1;
}
private readonly Worker1 _worker1;
public void Execute(IJobExecutionContext context)
{
this._worker1.Callback();
}
}
If it is not possible you can use Keyed service :
public class MonitorJob : IJob
{
public MonitorJob([WithKey("Worker1")]ICallback callback)
{
this._callback = callback;
}
private readonly ICallback _callback;
public void Execute(IJobExecutionContext context)
{
this._callback.Callback();
}
}
and you will have to register it this way cb.RegisterType<Worker1>().Keyed<ICallback>("Worker1")
If it is still not possible to modify your code this way, you can create a new module to add parameters :
public class CallbackModule : Module
{
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
{
if (registration.Services.OfType<TypedService>()
.Any(s => s.ServiceType == typeof(IJob)))
{
registration.Preparing += (sender, e) =>
{
// bind ICallback and IJob here
if (registration.Activator.LimitType == typeof(Job1))
{
e.Parameters = e.Parameters
.Concat(new TypedParameter[] { TypedParameter.From<ICallback>(e.Context.Resolve<Worker1>()) });
}
};
}
}
}
And the registration can be made this way :
ContainerBuilder cb = new ContainerBuilder();
cb.RegisterModule(new CallbackModule());
cb.RegisterType<Worker1>().As<ICallback>().AsSelf();
cb.RegisterType<Worker2>().As<ICallback>().AsSelf();
IContainer container = cb.Build();
By the way, the following code contains a mistake:
using (container.BeginLifetimeScope())
{
workers.Add(container.Resolve<Worker1>());
workers.Add(container.Resolve<Worker2>());
}
You create a new ILifetimeScope but don't use it. The following code should be use instead :
using (ILifetimeScope scope = container.BeginLifetimeScope())
{
workers.Add(scope.Resolve<Worker1>());
workers.Add(scope.Resolve<Worker2>());
}
If you want to be notified when the execute method is called, you can implement a JobListener for this. You can create one listener for all jobs or multiple listeners, one per job type perhaps, and then apply the appropriate matcher. Here's a link that might be useful with more details on job listeners and listeners in general: Quartz.Net Job Listeners, Part 3 of Quartz.net Listeners in detail