Reputation: 526
I am trying to execute a Quartz scheduler job in .NET with a non-empty constructor and I try to use the default Dependency Injection of .NET to supply the dependencies. This is my job class which needs a dependency injection
public class MyJob : IJob
{
private readonly ILogger _logger;
public MyJob(ILogger<MyJob> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public Task Execute(IJobExecutionContext context)
{
_logger.LogDebug("It's working!");
}
}
And this is how I build the job
IJobDetail jobDetail = JobBuilder.Create<MyJob>()
.WithIdentity("MyID", "MyGroup")
.Build();
var triggerBuilder = TriggerBuilder.Create()
.WithIdentity("MyID")
.StartAt(DateTime.Now)
.WithCronSchedule("*/1 * * * * ?"); // Every second
var trigger = triggerBuilder.Build();
_scheduler.ScheduleJob(jobDetail, trigger)
Now, I have defined in my app configuration the following:
// Quartz configuration.
services.AddQuartz(q =>
{
// Add dependency injection.
q.UseMicrosoftDependencyInjectionScopedJobFactory(options =>
{
// if we don't have the job in DI, allow fallback
// to configure via default constructor
options.AllowDefaultConstructor = true;
});
});
services.AddTransient<MyJob>();
// Also tried services.AddTransient<IJob, MyJob>();
as defined in the documentation on DI. Yet when I rebuild my solution and run the server, the following error is thrown:
Quartz.SchedulerException: Problem instantiating class 'MyProject.MyNamespace.Myjob: Cannot instantiate type which has no empty constructor Parameter name: MyJob' ---> System.ArgumentException: Cannot instantiate type which has no empty constructor
Yet, I explicitly setup MS DI for Quartz to use following their documentation. So how can I inject dependencies? I am using Quartz 3.2.4 and I installed the Quartz.Extensions.DependencyInjection
package (also 3.2.4).
Upvotes: 11
Views: 13713
Reputation: 69
I was also looking for the answer how to do dependency injection in order to inject serives into a job using non parameterless constructor. I did not find a simple answer in quartz doc or anywhere, so based on some found code I created a simple example that regsters services, creates a job and triggers it forever. It is a simple console app, just to understand a concept. I hope it helps someone to get the answer I needed way to much time for.
Most important code parts: Program.cs
// See https://aka.ms/new-console-template for more information
using Microsoft.Extensions.DependencyInjection;
using Quartz;
using Quartz.Impl;
using QuartzNetExample;
IServiceCollection services;
IServiceProvider provider;
//Qurtz
ISchedulerFactory schedulerFactory;
IScheduler _scheduler;
Console.WriteLine("Output from listners in debug console.");
services = new ServiceCollection();
schedulerFactory = new StdSchedulerFactory();
_scheduler = await schedulerFactory.GetScheduler();
//Register services
services.AddTransient<SimpleJob>();
services.AddSingleton<IEmailService, EmailService>();
provider = services.BuildServiceProvider();
//Setup job
_scheduler.JobFactory = new MyJobFactory(provider);
await _scheduler.Start();
_scheduler.ListenerManager.AddTriggerListener(new TriggerListener());
_scheduler.ListenerManager.AddJobListener(new JobListener());
_scheduler.ListenerManager.AddSchedulerListener(new SchedulerListener());
IJobDetail job = JobBuilder.Create<SimpleJob>()
.UsingJobData("username", "devhow")
.UsingJobData("password", "Security!!")
.WithIdentity("simplejob", "quartzexamples")
.StoreDurably()
.RequestRecovery()
.Build();
job.JobDataMap.Put("user", new JobUserParameter { Username = "devhow", Password = "Security!!" });
ITrigger trigger = TriggerBuilder.Create()
.WithIdentity("testtrigger", "quartzexamples")
.StartNow()
.WithSimpleSchedule(z => z.WithIntervalInSeconds(5).RepeatForever().WithMisfireHandlingInstructionIgnoreMisfires())
.Build();
await _scheduler.ScheduleJob(job, trigger);
//Run until the user presses a key
Console.WriteLine("Press key to terminate the job.");
Console.ReadKey();
SimpleJob.cs
using Quartz;
namespace QuartzNetExample;
public class SimpleJob : IJob
{
IEmailService _emailService;
public SimpleJob(IEmailService emailService)
{
try
{
_emailService = emailService;
}
catch (Exception e)
{
throw new InvalidOperationException("Error constructing job.", e);
}
}
public async Task Execute(IJobExecutionContext context)
{
try
{
_emailService.Send("[email protected]", "Quartz.net DI", "Dependency injection in quartz");
}
catch (Exception e)
{
throw new JobExecutionException($"Error executing task.",e );
}
}
}
MyJobFactory.cs
using Microsoft.Extensions.DependencyInjection;
using Quartz;
using Quartz.Simpl;
using Quartz.Spi;
namespace QuartzNetExample;
public class MyJobFactory : SimpleJobFactory
{
IServiceProvider _provider;
public MyJobFactory(IServiceProvider serviceProvider)
{
_provider = serviceProvider;
}
public override IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
try
{
// this will inject dependencies that the job requires
return (IJob)_provider.GetService(bundle.JobDetail.JobType);
}
catch (Exception e)
{
throw new SchedulerException(string.Format("Problem while instantiating job '{0}' from the Aspnet Core IOC.", bundle.JobDetail.Key), e);
}
}
}
EmailService.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace QuartzNetExample;
public interface IEmailService
{
void Send(string receiver, string subject, string body);
}
public class EmailService : IEmailService
{
public void Send(string receiver, string subject, string body)
{
Console.WriteLine($"{DateTime.Now.ToString("hh-mm-ss")} Sending email to {receiver} with subject {subject} and body {body}");
}
}
Upvotes: 1
Reputation: 6884
You should register your jobs and triggers within the AddQuartz
. If you look at the official documentation you will see that the ScheduleJob
/AddJob
/AddTrigger
calls are done within the callback which ensures that the DI works. This will probably change in 3.3 version and the job registration won't be that strict anymore.
Upvotes: 2