HOW TO use Quartz.NET in PRO way?

September 21, 2009 | by Maciej Greń

Comments

logo

Have you ever needed or thought of scheduling  some work of your enterprise .NET application?  Maybe you have heard about Quartz.NET. I’ve heard about it recently and I had to use it in my system. I was delighted with the possibilities it offered. But very quick I noticed that I cannot inject dependency into my jobs by using my IoC container. Fortunately, I’ve found the solution and I would like to share it  in this post.

Quartz.NET at a glance

Quartz.NET is an open source job scheduling system. It is a port of a very popular open source Java job scheduling framework, Quartz . Quartz.NET is pure .NET library written in C#. Quartz.NET developers say that this project is feature-wise equal to Quartz Java 1.6 excluding Java specifics.

Quartz.NET has a very good home page, where you can find the list of features, API documentation and a very useful tutorial.

Create Quartz.NET’s job using IoC and DI

Below, I describe how to use Quartz.NET with IoC and DI container, in my case- Castle Windsor. If you don’t know much about these patterns, you can find out more about  it in this great article.

I’ve used these patterns to simplify dependency creation in my job objects. Of course, I have other benefits, which resulted from using IoC and DI, but in this post the main focus is on how to instantiate job using IoC an DI. In Quartz.NET tutorial I’ve found one useful hint:”The job is instantiated via the JobFactory configured on the Scheduler. You may want to create your own implementation of JobFactory to accomplish things such as having your application’s IoC or DI container produce/initialize the job instance.”

Let’s do it in 5 steps

1. Create your own implementation of JobFactory. How? I’ve used implementation of Quartz.NET’s SimpleJobFactory.

  1. public class IoCJobFactory : IJobFactory
  2.  {
  3.      // Castle Windsor conatainer locator
  4.      readonly IServiceLocator locator;
  5.  
  6.      public IoCJobFactory(IServiceLocator locator)
  7.      {
  8.          this.locator = locator;
  9.      }
  10.  
  11.      public IJob NewJob(TriggerFiredBundle bundle)
  12.      {
  13.          try
  14.          {
  15.              JobDetail jobDetail = bundle.JobDetail;
  16.              Type jobType = jobDetail.JobType;
  17.  
  18.              // Return job that is registrated in container
  19.              return (IJob)locator.GetInstance(jobType);
  20.          }
  21.          catch (Exception e)
  22.          {
  23.              SchedulerException se = new SchedulerException(
  24.                                      "Problem instantiating class", e);
  25.              throw se;
  26.          }
  27.      }
  28.  }

IServiceLocator is the most important in this class. This is interface for IoC container location. When you use it, you can get instance of registered job type.

2. Now make implementation of IServiceLocator.

  1. public class WindsorServiceLocator : IServiceLocator
  2.  {
  3.      private readonly IWindsorContainer container;
  4.  
  5.      public WindsorServiceLocator(IWindsorContainer container)
  6.      {
  7.          this.container = container;
  8.      }
  9.  
  10.      public object GetService(Type serviceType)
  11.      {
  12.          return container.GetService(serviceType);
  13.      }
  14.  
  15.      public object GetInstance(Type serviceType)
  16.      {
  17.          return container.Resolve(serviceType);
  18.      }
  19.  
  20.      public object GetInstance(Type serviceType, string key)
  21.      {
  22.          if (key != null)
  23.              return container.Resolve(key, serviceType);
  24.          return container.Resolve(serviceType);
  25.      }
  26.  
  27.      public IEnumerable<object> GetAllInstances(Type serviceType)
  28.      {
  29.          return (object[])container.ResolveAll(serviceType);
  30.      }
  31.  
  32.      public TService GetInstance<TService>()
  33.      {
  34.          return container.Resolve<TService>();
  35.      }
  36.  
  37.      public TService GetInstance<TService>(string key)
  38.      {
  39.          return container.Resolve<TService>(key);
  40.      }
  41.  
  42.      public IEnumerable<TService> GetAllInstances<TService>()
  43.      {
  44.          return container.ResolveAll<TService>();
  45.      }
  46.  }

3. In this step, create some simple job with dependencies. Mine looks like in this way:

  1. class MonitorJob : IJob
  2.  {
  3.      private readonly TimerScheduler scheduler;
  4.      private XmlDataReader dataReader;
  5.  
  6.      public MonitorMailTimeJob(ILoadServiceScheduler scheduler, IDataReader dataReader)
  7.      {
  8.          this.scheduler = (TimerScheduler)scheduler;
  9.          this.dataReader = (XmlDataReader) dataReader;
  10.      }
  11.  
  12.      public void Execute(JobExecutionContext context)
  13.      {
  14.          ProcessData();
  15.      }
  16.  
  17.      // … further part of code
  18.  }

4. The next step is registering components in Castle container:

  • implementation of IServiceLocator
  • IoCJobFactory
  • IScheduler using ISchedulerFactory GetScheduler method – this provides setting IoCJobFactory on the Scheduler component
  • Job component

You can do it either in code or in configuration file. I’ve done it  in code.

  1. var container = new WindsorContainer();
  2.  
  3.  // important to use Factories in conatiner
  4.  container.AddFacility<FactorySupportFacility>();
  5.  
  6.  container.Register(Component.For<IServiceLocator>()
  7.      .Instance( new WindsorServiceLocator( conatiner ) ),
  8.      Component.For<IJobFactory>().ImplementedBy<IoCJobFactory>(),
  9.      Component.For<ISchedulerFactory>()
  10.      .ImplementedBy<StdSchedulerFactory>(),
  11.      Component.For<IScheduler>()
  12.      .UsingFactory( ( ISchedulerFactory factory ) =>
  13.                     factory.GetScheduler() ),
  14.      Component.For<ILoadServiceScheduler>()
  15.      .ImplementedBy<TimerScheduler>(),
  16.      Component.For<IDataReader>().ImplementedBy<XmlDataReader>(),
  17.      Component.For<MonitorJob>().LifeStyle.Transient );

5. The last step involves resolving Scheduler component.

  1. IScheduler scheduler = container.Resolve<IScheduler>();

That’s all.

Final thoughts

In my application these solutions work fine. I don’t worry about creating new objects that jobs are dependent on. If you decide to use Quartz.NET, I hope you also use this solution to build professional applications using IoC and DI patterns.

Share your opinion and experience with us below or meet us on Twitter: @GOYELLO.

Thanks to Karol Świder for helping me write this blog post.

  • Striz
    I think it's too complex for small things that needs to be scheduled. I know for a fact that lots of guys have this need but are too lazy :) I actually prefer to use components or services if I need anything complex for small things. So I saw this post of yours and thought I'd mention some online solutions that I tried before. The WebBasedCron at webbasedcron.com is simple to use but doesn't offer free stuff. The Web Scheduler at scheduler.codeeffects.com has really cool UI. It gives you a free schedule but has too much of extras, I think. The Web Service Scheduler at wsscheduler.com is oversimplified but is fine if you don't need any complex schedules.
  • You might be interested in this Quartz facility for Windsor: http://bugsquash.blogspot.com/2009/03/windsor-f...
    It supports listener integration and doesn't depend on CommonServiceLocator
  • maciejgren
    Thank you! for sure I will have a look there!
blog comments powered by Disqus