39

Currently I am writing a service using Quartz.NET to schedule the running of it.

I was wondering if anyone has any experience of using constructor injection with Quartz.NET and Simple Injector.

Below is essentially what I wish to achieve

public class JobImplementation: IJob
{
    private readonly IInjectedClass injectedClass;

    public JobImplementation(IInjectedClass _injectedClass)
    {
         injectedClass = _injectedClass
    }

    public void Execute(IJobExecutionContext _context)
    {
        //Job code
    }
Steven
  • 166,672
  • 24
  • 332
  • 435
Thewads
  • 4,953
  • 11
  • 55
  • 71

3 Answers3

46

According to this blog post, you would need to implement a custom IJobFactory, like this:

public class SimpleInjectorJobFactory : IJobFactory
{
    private readonly Container container;
    private readonly Dictionary<Type, InstanceProducer> jobProducers;

    public SimpleInjectorJobFactory(
        Container container, params Assembly[] assemblies)
    {
        this.container = container;

        // By creating producers, jobs can be decorated.
        var transient = Lifestyle.Transient;
        this.jobProducers =
            container.GetTypesToRegister(typeof(IJob), assemblies).ToDictionary(
                type => type,
                type => transient.CreateProducer(typeof(IJob), type, container));
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler _)
    {
        var jobProducer = this.jobProducers[bundle.JobDetail.JobType];
        return new AsyncScopedJobDecorator(
            this.container, () => (IJob)jobProducer.GetInstance());
    }

    public void ReturnJob(IJob job)
    {
        // This will be handled automatically by Simple Injector
    }

    private sealed class AsyncScopedJobDecorator : IJob
    {
        private readonly Container container;
        private readonly Func<IJob> decorateeFactory;

        public AsyncScopedJobDecorator(
            Container container, Func<IJob> decorateeFactory)
        {
            this.container = container;
            this.decorateeFactory = decorateeFactory;
        }

        public async Task Execute(IJobExecutionContext context)
        {
            using (AsyncScopedLifestyle.BeginScope(this.container))
            {
                var job = this.decorateeFactory();
                await job.Execute(context);
            }
        }
    }
}

Furthermore, you'll need the following registrations:

var container = new Container();

container.Options.ScopedLifestyle = new AsyncScopedLifestyle();

var factory = new StdSchedulerFactory();

IScheduler scheduler = await factory.GetScheduler();

scheduler.JobFactory = new SimpleInjectorJobFactory(
    container, 
    Assembly.GetExecutingAssembly()); // assemblies that contain jobs

// Optional: register some decorators
container.RegisterDecorator(typeof(IJob), typeof(LoggingJobDecorator));

container.Verify();
Steven
  • 166,672
  • 24
  • 332
  • 435
  • 1
    Thanks for this. A slight question, the class of StdSchedulerFactory has 2 constructors, is there anyway of telling simpleinjector to use the zero param constructor? – Thewads Jan 28 '13 at 14:29
  • @Thewads: I updated my answer. Since `StdSchedulerFactory` is a singleton and a Stable Dependency (part of a framework) it is safe (perhaps even advisable) to manually create it. – Steven Jan 28 '13 at 16:21
  • 9
    I had to search for it and thought it would be helpful to note that the following quartz configuration is how you tell it to use the injection friendly job factory implementation: – nelsestu Mar 11 '13 at 17:00
  • 9
    I had to register the scheduler slightly differently to above to put the jobFactory in. Using XML did not work since it forces a default constructor requirement. So in addition to Steven's post I added container.RegisterSingle(() => {var scheduler = schedulerFactory.GetScheduler(); scheduler.JobFactory = container.GetInstance(); return scheduler; }); – Jafin May 17 '13 at 01:01
  • What is `isDecorator`? Also, the type of `jobProducers` should be `Dictionary`. – Mrchief Feb 22 '15 at 02:10
  • 2
    Great! Also note that with Quartz.Net 2.0, one needs to implement `public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler) {...}`. And not sure if there should be any cleanup call implemented in `public void ReturnJob(IJob job){...}`. – Mrchief Feb 22 '15 at 15:27
  • 1
    This doesn't seem to work with the latest version of Quartz and SimpleInjector. I do not have an ILoadServiceScheduler, TimerScheduler, LoggingJobDecorator. Also how do I get the applicationAssemblies? I'm quite confused here and can't figure this one out. :) – Schoof Jun 26 '17 at 07:58
  • For anyone having issues with this, have a look at a working example here: https://github.com/tynor88/Topshelf.SimpleInjector – ogrim Dec 13 '17 at 11:19
  • 1
    I was having some issue, and suddenly found my old comment again. Now, for myself and others: here is the full example I was referencing before: https://github.com/tynor88/Topshelf.SimpleInjector/blob/dev/Samples/Topshelf.SimpleInjector.Quartz.Decorators.Sample/Program.cs – ogrim Feb 19 '18 at 09:23
  • 1
    Quartz V3 implementation https://stackoverflow.com/questions/49057860/constructor-injection-with-quartz-net-3-0-3-and-simple-injector-how-to – live2 Jun 14 '18 at 12:54
2

Late to the party, but https://github.com/hbiarge/Quartz.Unity works well for combining Quartz.NET and Unity.

IUnityContainer container = new UnityContainer();
container.AddNewExtension<Quartz.Unity.QuartzUnityExtension>();
// do your other Unity registrations
IScheduler scheduler = container.Resolve<IScheduler>();

scheduler.ScheduleJob(
    new JobDetailImpl(myCommandName, typeof(MyCommand)),
    TriggerBuilder.Create()
        .WithCronSchedule(myCronSchedule)
        .StartAt(startTime)
        .Build()
);
scheduler.Start();
howcheng
  • 2,211
  • 2
  • 17
  • 24
1

There are few steps to use Quartz.net with dependency injection engine from asp.net core.

Add nuget package to your project:

Microsoft.Extensions.DependencyInjection

Create custom JobFactory:

public class JobFactory : IJobFactory
{
    protected readonly IServiceProvider _serviceProvider;

     public JobFactory(IServiceProvider serviceProvider) 
         => _serviceProvider = serviceProvider;

     public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
         => _serviceProvider.GetService(bundle.JobDetail.JobType) as IJob;

     public void ReturnJob(IJob job) 
         => (job as IDisposable)?.Dispose();
 }

Specify JobFactory when configuring scheduler:

 var scheduler = await StdSchedulerFactory.GetDefaultScheduler();
 scheduler.JobFactory = new JobFactory(_serviceProvider);

For someone can be usefull example of win service with Quartz.net and DI (from asp.net core) on the board:

public class WinService : ServiceBase
{
    private Scheduler _scheduleManager;

    private readonly Startup _startup;

    public WinService()
    {
        ServiceName = "SomeWinService";
        _startup = new Startup();
    }

    static void Main(string[] args)
    {
        var service = new WinService();

        // Working as Windows-service
        if (Console.IsInputRedirected && Console.IsOutputRedirected)
        {
            ServiceBase.Run(service);
        }
        // Working as console app
        else
        {
            service.OnStart(args);
            Console.WriteLine("Press any key to stop...");
            Console.ReadKey();
            service.OnStop();
        }
    }
 
    protected override void OnStart(string[] args)
    {
        _startup.RegisterServices();
        _scheduleManager = new Scheduler(_startup.ServiceProvider);
        _scheduleManager.StartTracking().Wait();
    }

    protected override void OnPause()
        => _scheduleManager.PauseTracking().Wait();

    protected override void OnContinue()
        => _scheduleManager.ResumeTracking().Wait();
 
    protected override void OnStop()
    {
        _scheduleManager.StopTracking().Wait();
        _startup.DisposeServices();
    }
}

public class Startup
{
    private IServiceProvider _serviceProvider;

    public IServiceProvider ServiceProvider => _serviceProvider;

    public void RegisterServices()
    {        
        _serviceProvider = new ServiceCollection()
            //.AddTransient(...)
            //.AddScoped(...)
            //.AddSingleton(...)
            .BuildServiceProvider();

    }

    public void DisposeServices()
    {
        if (_serviceProvider == null)
            return;

        if (_serviceProvider is IDisposable)
        {
            ((IDisposable)_serviceProvider).Dispose();
        }
    }
}

public class Scheduler
{        
    private readonly IServiceProvider _serviceProvider;
   
    private IScheduler _scheduler;
   
    public Scheduler(IServiceProvider serviceProvider) 
        => _serviceProvider = serviceProvider;
   
    public async Task StartTracking()
    {
        _scheduler = await StdSchedulerFactory.GetDefaultScheduler();
        _scheduler.JobFactory = new JobFactory(_serviceProvider);
        await _scheduler.Start();
       
        // Schedule your jobs here
    }
  
    public async Task PauseTracking() => await _scheduler?.PauseAll();
   
    public async Task ResumeTracking() => await _scheduler?.ResumeAll();
  
    public async Task StopTracking() => await _scheduler?.Shutdown();
}
BorisSh
  • 531
  • 4
  • 3