Wouter Vandenputte
Wouter Vandenputte

Reputation: 526

Quartz.net: How to create jobs using Dependency Injection

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

Answers (2)

MaciejW
MaciejW

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.

DI Github Example

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

Marko Lahma
Marko Lahma

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

Related Questions