Home » Good Practices » Topshelf and Quartz.NET with dependency injection

Topshelf and Quartz.NET with dependency injection

In the previous post about Topshelf and Quartz.NET I mentioned that there is a chance to configure the inversion of control container instead of manually pointing which implementation we want to use. In this post, I will explain how to do just that, using a few popular inversion of control containers – Ninject and StructureMap. If you prefer any other container, please let me know. I’ll do my best to include it here.

Plain project

To explain the problem with Dependency Injection I’ll slightly modify the example from my previous post. I am going to add an injected dependency, IDateProvider to the MyJob class. All code changes you might find below:

class Program
{
    static void Main(string[] args)
    {
        HostFactory.Run(x =>
        {
            x.Service<MyService>(s =>
            {
                s.ConstructUsing(() => new MyService());
                s.WhenStarted(service => service.OnStart());
                s.WhenStopped(service => service.OnStop());
 
                // Replace default factory with MyJobFactory
                s.UsingQuartzJobFactory(() => new MyJobFactory());
 
                s.ScheduleQuartzJob(q =>
                    q.WithJob(() =>
                        JobBuilder.Create<MyJob>().Build())
                        .AddTrigger(() => TriggerBuilder.Create()
                            .WithSimpleSchedule(b => b
                                .WithIntervalInSeconds(10)
                                .RepeatForever())
                            .Build()));
            });
 
            x.RunAsLocalSystem()
                .DependsOnEventLog()
                .StartAutomatically()
                .EnableServiceRecovery(rc => rc.RestartService(1));
 
            x.SetServiceName("My Topshelf Service");
            x.SetDisplayName("My Topshelf Service");
            x.SetDescription("My Topshelf Service's description");
        });
    }
}
 
public class MyService
{
    public void OnStart()
    {            
    }
 
    public void OnStop()
    {
    }
}
 
public class MyJob : IJob
{
    private readonly IDateProvider _dateProvider;
        
    public MyJob(IDateProvider dateProvider)
    {
        _dateProvider = dateProvider;
    }
 
    public void Execute(IJobExecutionContext context)
    {
        Console.WriteLine($"[{_dateProvider.GetCurrentDateTime}] Welcome from MyJob!");
    }
}
 
public class DateProvider : IDateProvider
{
    public DateTime GetCurrentDateTime => DateTime.UtcNow;
}
 
public interface IDateProvider
{
    DateTime GetCurrentDateTime { get; }
}

This current implementation requires me to code my own implementation of the IJobFactory interface. The default implementation uses only parameterless constructors. In my case, the program would not work. This is because a class has only one MyJob constructor with injected IDateProvider implementation. MyJobFactory code is below:

public class MyJobFactory : IJobFactory
{
    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        // Normally I would resolve it using bundle.JobDetail.JobType
        // but this is only demo, so...
        return new MyJob(new DateProvider());
    }
 
    public void ReturnJob(IJob job)
    {
    }
}

Looks easy, right? Let’s see how it’s gonna work with popular IoC containers.

Ninject

I won’t explain how Ninject works. It’s not the purpose of this post. You may find Ninject’s documentation here.

To separate interface-implementation bindings I’ll create a separate class. In Ninject world it’s called a Module.

Check MyNinjectModule implementation:

public class MyNinjectModule : NinjectModule
{
    public override void Load()
    {
        // Bind it as Singleton
        Bind<IDateProvider>().To<DateProvider>().InSingletonScope();
    }
}

There’s a Nuget package which will help you resolve all previously registered implementations.

install-package Topshelf.NInject

My application with Ninject implementation:

class Program
{
    static void Main(string[] args)
    {
        HostFactory.Run(x =>
        {
            // Replace default factory with Ninject implementation
            x.UseNinject(new MyNinjectModule());
            x.UsingQuartzJobFactory(() => new NinjectJobFactory(NinjectBuilderConfigurator.Kernel));
 
            x.Service<MyService>(s =>
            {
                // Point to use Ninject
                s.ConstructUsingNinject();
                s.WhenStarted(service => service.OnStart());
                s.WhenStopped(service => service.OnStop());
 
                s.ScheduleQuartzJob(q =>
                    q.WithJob(() =>
                        JobBuilder.Create<MyJob>().Build())
                        .AddTrigger(() => TriggerBuilder.Create()
                            .WithSimpleSchedule(b => b
                                .WithIntervalInSeconds(10)
                                .RepeatForever())
                            .Build()));
            });
 
            x.RunAsLocalSystem()
                .DependsOnEventLog()
                .StartAutomatically()
                .EnableServiceRecovery(rc => rc.RestartService(1));
 
            x.SetServiceName("My Topshelf Service");
            x.SetDisplayName("My Topshelf Service");
            x.SetDescription("My Topshelf Service's description");
        });
    }
}

Similar as without a container, we are required to create our own IJobFactory implementation. I’m sure I would find an existing solution on Nuget servers, however, the implementation is very simple, so I decided to write it on my own. Check NinjectJobFactory code:

public class NinjectJobFactory : IJobFactory
{
    private readonly IResolutionRoot _resolutionRoot;
 
    public NinjectJobFactory(IResolutionRoot resolutionRoot)
    {
        _resolutionRoot = resolutionRoot;
    }
 
    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        return _resolutionRoot.Get(bundle.JobDetail.JobType) as IJob;
    }
 
    public void ReturnJob(IJob job)
    {
        _resolutionRoot.Release(job);
    }
}

Code is easy. Just pass Ninject’s kernel object into NinjectJobFactory constructor and every time Quartz will ask for specific IJob implementation, resolve it using Get method and pass bundle.JobDetail.JobType property.

StructureMap

Next container I would like to present here is StructureMap. You may find documentation here.

Topshelf and Quartz.NET have it’s own StructureMap implementation on Nuget. To use it get those packages:

install-package Topshelf.StructureMap
install-package Topshelf.Quartz.StructureMap

Below you’ll find modified console app which uses StructureMap as IoC container.

class Program
{
    static void Main(string[] args)
    {
        HostFactory.Run(x =>
        {
            var container = new Container(cfg =>
            {
                cfg.For<IDateProvider>().Singleton().Use<DateProvider>();
            });
 
            // Replace default factory with StructureMap implementation
            x.UseStructureMap(container);
 
            x.Service<MyService>(s =>
            {
                // Point to use StructureMap
                s.ConstructUsingStructureMap();
                s.WhenStarted(service => service.OnStart());
                s.WhenStopped(service => service.OnStop());
 
                // Use StructureMap to resolve Quartz's jobs
                s.UseQuartzStructureMap();
 
                s.ScheduleQuartzJob(q =>
                    q.WithJob(() =>
                        JobBuilder.Create<MyJob>().Build())
                        .AddTrigger(() => TriggerBuilder.Create()
                            .WithSimpleSchedule(b => b
                                .WithIntervalInSeconds(10)
                                .RepeatForever())
                            .Build()));
            });
 
            x.RunAsLocalSystem()
                .DependsOnEventLog()
                .StartAutomatically()
                .EnableServiceRecovery(rc => rc.RestartService(1));
 
            x.SetServiceName("My Topshelf Service");
            x.SetDisplayName("My Topshelf Service");
            x.SetDescription("My Topshelf Service's description");
        });
    }
}

There’re few differences between this and previous implementation.

First difference is the fact that Ninject loads bindings in separate class called Module where StructureMap loads registrations when applications starts (StructureMap has similar feature to load bindings stored in separate classes – check StructureMap’s Profile for more info). I wanted to show different ways how to load registrations.

The next difference is the way how Quartz.NET resolves IJob implementation. I don’t have to write my own implementation of IJobFactory because Topshelf.Quartz.StructureMap provides it by executing UseQuartzStructureMap().

Summary

In this post I presented an easy way how to implement dependency injection in Topshelf and Quartz.NET applications. If you prefer to write your own IJobFactory implementation, it’s very easy. However, if you prefer to use ready solutions and save your time, check Nuget. There is a high chance somebody else has already came across the same problem and put libraries onto Nuget repository.

If you would like to see more Topshelf & Quartz.NET inversion of control implementations, please let me know in comments. I’ll try to update the post once requested.

 

 

Published by

Mateusz Pustelak

Software Developer with several years of commercial experience, TDD practitioner, DDD/CQRS fan. Currently working for Universal Music Group in London.

2 thoughts on “Topshelf and Quartz.NET with dependency injection”

  1. This article is the best thing I’ve ever read. However, I still have some questions. MATEUSZ PUSTELAK, can I email to ask about some issues in the writing?

Leave a Reply

Your email address will not be published. Required fields are marked *

*