Developing Windows Service with Autofac, Atlas and Quartz.NET

There are many articles about implementing a scheduled job using Autofac and Quartz.NET on the Internet. However, they are not quite complete. They just provide some concepts but not actual working example. In general, this post follows Mark Jourdan‘s post, A quick way to create a windows service using Autofac, Quartz and Atlas. However, it actually didn’t go into how to run method from resolved instances. Here in this post, I’ll walkthrough how to develop a Windows Service using Autofac, Atlas and Quartz.NET with some corrections of Mark’s post. The source code used for this post can be found here:

Downloading NuGet Packages

In order for this application to get working, several NeGet package libraries need to be installed before starting.

NOTE: At the time of writing this post, the version of Autofac is 3.4.0 but Atlas only supports up to 3.3.1 version of Autofac. Please make sure this.

Preparing Console Application for Atlas

With, Atlas, a console application can easily turn into a Windows Service application. So, let’s create a console application project into a solution file.

First of all, App.config needs to be setup for Atlas.













Now, have a look at the following code snippet.



///
/// This represents the Windows Service application entity.
///

internal class Program
{
private static readonly ILog Log = LogManager.GetCurrentClassLogger();

///
/// This represents the main entry point of the Windows Service application.
///

/// List of arguments.
static void Main(string[] args)
{
try
{
var configuration = Host.UseAppConfig() // #1
.AllowMultipleInstances() // #2
.WithRegistrations(p => p.RegisterModule(new SampleModule())); // #3
if (args != null && args.Any())
configuration = configuration.WithArguments(args); // #4

Host.Start(configuration); // #5
}
catch (Exception ex)
{
Log.Fatal(“Exception during startup.”, ex);
Console.ReadLine();
}
}
}

  • #1: Let Atlas know that SampleService is run as a Windows Service.
  • #2: Let Atlas allow to run multiple instances. Comment or remove this, if not necessary.
  • #3: Register the IoC container built with Autofac
  • #4: Add arguements, if provided.
  • #5: Start Atlas.

Both #1 and #3 are the most crucial part of the post. #1 defines the actual Windows Service to run and #3 defines IoC container for dependency injection. First comes first. Let’s start with #1.

Implementing SampleService

SampleService must implement the IAmAHostedProcess interface to be run on top of Atlas. First of all, App.config needs CronExpression value within the <appSettings> element.







This value tells the scheduler to run the job per every 10 seconds. Now, let’s implement the SampleService class for the actual Windows Service.



///
/// This represents an entity for Windows Service hosted by Atlas.
///

internal class SampleService : IAmAHostedProcess
{
private static readonly ILog Log = LogManager.GetCurrentClassLogger();

///
/// Gets or sets the scheduler instance.
///

public IScheduler Scheduler { get; set; } // #1

///
/// Gets or sets the job factory instance.
///

public IJobFactory JobFactory { get; set; } // #2

///
/// Gets or sets the job listener instance.
///

public IJobListener JobListener { get; set; } // #3

///
/// Starts the Windows Service.
///

public void Start()
{
Log.Info(“Sample Windows Service starting”);

var job = JobBuilder.Create()
.WithIdentity(“SampleJob”, “SampleWindowsService”)
.Build(); // #4

var trigger = TriggerBuilder.Create()
.WithIdentity(“SampleTrigger”, “SampleWindowsService”)
.WithCronSchedule(ConfigurationManager.AppSettings[“CronExpression”]) // #5
.ForJob(“SampleJob”, “SampleWindowsService”)
.Build(); // #6

this.Scheduler.JobFactory = this.JobFactory; // #7
this.Scheduler.ScheduleJob(job, trigger); // #8
this.Scheduler.ListenerManager.AddJobListener(this.JobListener); // #9
this.Scheduler.Start(); // #10

Log.Info(“Sample Windows Service started”);
}

///
/// Stops the Windows Service.
///

public void Stop()
{
Log.Info(“Sample Windows Service stopping”);

this.Scheduler.Shutdown();

Log.Info(“Sample Windows Service stopped”);
}

///
/// Resumes the Windows Service.
///

public void Resume()
{
Log.Info(“Sample Windows Service resuming”);

this.Scheduler.ResumeAll();

Log.Info(“Sample Windows Service resumed”);
}

///
/// Pauses the Windows Service.
///

public void Pause()
{
Log.Info(“Sample Windows Service pausing”);

this.Scheduler.PauseAll();

Log.Info(“Sample Windows Service paused”);
}
}

  • #1: IScheduler instance is injected through the IoC container.* #2: IJobFactory instance is injected through the IoC container.
  • #3: IJobListener instance is injected through the IoC container.
  • #4: Builds a SampleJob instance.
  • #5: Lets a trigger to run the SampleJob instance on the schedule using the cron expression.
  • #6: Builds a trigger instance.
  • #7: Let the IScheduler instance to resolve instances built through the IoC container.
  • #8: Schedule the SampleJob instance with the trigger built.
  • #9: Adds a IJobListener instance while the SampleJob is being executed.
  • #10: Starts the IScheduler instance.

IScheduler instance is defined in the IoC container and injected into the SampleService instance. Make sure, both IJobFactory and IJobListener instances are also injected from the IoC container. The IoC container is defined by the SampleModule instance which comes to the next section.

Implementing SampleModule

SampleModule works as an IoC container with Autofac. With this, all instances used in this Windows Service are registered and resolved. SampleModule inherits the Autofac.Module class.



///
/// This represents an entity for Autofac module.
///

internal class SampleModule : Module
{
///
/// Override to add registrations to the container.
///

/// The builder through which components can be registered.
protected override void Load(ContainerBuilder builder)
{
this.LoadQuartz(builder);
this.LoadServices(builder);
this.LoadLogicLayers(builder);
}

///
/// Loads the quartz scheduler instance.
///

/// The builder through which components can be registered.
private void LoadQuartz(ContainerBuilder builder)
{
builder.Register(c => new StdSchedulerFactory().GetScheduler())
.As()
.InstancePerLifetimeScope(); // #1
builder.Register(c => new SampleJobFactory(ContainerProvider.Instance.ApplicationContainer))
.As(); // #2
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(p => typeof (IJob).IsAssignableFrom(p))
.PropertiesAutowired(); // #3
builder.Register(c => new SampleJobListener(ContainerProvider.Instance))
.As(); // #4
}

///
/// Loads the service instance.
///

/// The builder through which components can be registered.
private void LoadServices(ContainerBuilder builder)
{
builder.RegisterType()
.As()
.PropertiesAutowired(); // #5
}

///
/// Loads the logic layers.
///

/// The builder through which components can be registered.
private void LoadLogicLayers(ContainerBuilder builder)
{
builder.RegisterType()
.As(); // #6
}
}

  • #1: Registers the IScheduler instance.
  • #2: Registers the IJobFactory instance.* #3: Registers the IJob instance. This will resolve the SampleJob instance.
  • #4: Registers the IJobListener instance.
  • #5: Registers the IAmAHostedProcess instance. This will resolve the SampleService instance.
  • #6: Registers the ISampleLogicLayer instance. This will run the actual business logic.

When #5 is resolved, its properties will get IScheduler, IJobFactory and IJobListener instances injected. Let’s move onto the SampleJob class to execute the real job.

Implementing SampleJob

The SampleJob class actually runs the business logic instance resolved from the IoC container.



///
/// This represents an entity for the job that actually performs for the schedule.
///

internal class SampleJob : IJob
{
private static readonly ILog Log = LogManager.GetCurrentClassLogger();

public ISampleLogicLayer SampleLogicLayer { get; set; }

///
/// Called by the Quartz.IScheduler when a Quartz.ITrigger fires that is associated with the Quartz.IJob.
///

/// JobExecutionContext instance
public void Execute(IJobExecutionContext context)
{
Log.Info(“Application executing”);

this.SampleLogicLayer.Run();

Log.Info(“Application executed”);
}
}

And the SampleJob needs the ISampleLogicLayer instance.



///
/// This provides interfaces to the SampleLogicLayer class.
///

internal interface ISampleLogicLayer : IDisposable
{
///
/// Runs the business logic here.
///

void Run();
}



///
/// This represents an entity that performs the actual business logic.
///

internal class SampleLogicLayer : ISampleLogicLayer
{
private static readonly ILog Log = LogManager.GetCurrentClassLogger();

///
/// Runs the business logic here.
///

public void Run()
{
Log.Info(“This has been run”);
}

///
/// Disposes resources not being used any more.
///

public void Dispose()
{
}
}

Therefore, when the SampleJob instance is executed, it calls the method Run() of the ISampleLogicLayer instance. The Run() method writes a log into the logger instance. So far, we’ve implemented the core logics. However, Quartz.NET needs to know whether all necessary instances are resolved or not. Let’s move onto the next section to let Quartz.NET know the IoC container is ready for use.

Implementing SampleJobFactory

In order to let the IScheduler know all necessary instances are ready for use, an IJobFactory instance needs to be injected. Here’s a code snippet for the class implementing the IJobFactory interface.



///
/// This represents an entity to let IScheduler know the IoC container is ready for use.
///

internal class SampleJobFactory : IJobFactory
{
private readonly IContainer _container;

///
/// Initialises a new instance of the SampleJobFactory class.
///

/// IoC container instance.
public SampleJobFactory(IContainer container)
{
if (container == null)
throw new ArgumentNullException(“container”);

this._container = container;
}

///
/// Creates a new job resolved from the IoC container.
///

/// Trigger fired bundle instance.
/// Scheduler instance.
/// Returns a new job resolved from the IoC container.
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
if (bundle == null)
throw new ArgumentNullException(“bundle”);

return (IJob)this._container.Resolve(bundle.JobDetail.JobType); // #1
}

///
/// Allows the the job factory to destroy/cleanup the job if needed.
///

/// Job instance.
public void ReturnJob(IJob job)
{
}
}

  • #1: Returns the resolved job instance. In our example, it returns the SampleJob instance resolved from the IoC container.

Let’s move back to the SampleService section above. The SampleService gets this IJobFactory instance as a parameter and the IJobFactory instance is set to the ISchedule‘s JobFactory property. By doing so, instances that Autofac IoC container register and resolve are notified to the scheduler so that it runs the job correctly. Now, as a final section, implement SampleJobListener class.

Implementing SampleJobListener

SampleJobListener makes sure the job instance gets all necessary instances injected before executing the job and disposes all relevant resources after being executed.



///
/// This represents an entity for event handling while a job is running.
///

internal class SampleJobListener : IJobListener
{
private readonly IContainerProvider _provider;

private IUnitOfWorkContainer _container;

///
/// Initialises a new instance of the SampleJobListener class.
///

/// Container provider instance.
public SampleJobListener(IContainerProvider provider)
{
if (provider == null)
throw new ArgumentNullException(“provider”);

this._provider = provider;
this.Name = “SampleJobListener”;
}

///
/// Gets the name of the job listener.
///

public string Name { get; private set; }

///
/// Called by the Quartz.IScheduler when a Quartz.IJobDetail is about to be executed
/// (an associated Quartz.ITrigger has occurred).
///

/// JobExecutionContext instance.
///
/// This method will not be invoked if the execution of the Job was vetoed by a Quartz.ITriggerListener.
///

public void JobToBeExecuted(IJobExecutionContext context)
{
this._container = this._provider.CreateUnitOfWork();
this._container.InjectUnsetProperties(context.JobInstance);
}

///
/// Called by the Quartz.IScheduler when a Quartz.IJobDetail was about to be executed
/// (an associated Quartz.ITrigger has occurred), but a Quartz.ITriggerListener vetoed it’s execution.
///

/// JobExecutionContext instance.
public void JobExecutionVetoed(IJobExecutionContext context)
{
}

///
/// Called by the Quartz.IScheduler after a Quartz.IJobDetail has been executed,
/// and be for the associated Quartz.Spi.IOperableTrigger‘s Quartz.Spi.IOperableTrigger.Triggered(Quartz.ICalendar) method has been called.
///

/// JobExecutionContext instance.
/// JobExecutionException instance.
public void JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException)
{
this._container.Dispose();
}
}

As above, all the implementations have been completed. It’s now time to run this. In order to debug this application on the console mode, simple put the parameter of console on the debug mode.

Make sure that App.config needs to have the logging configuration like below.




















Once it’s done, punch F5 key for debug. Then you’ll see the result similar to the following screen:

Conclusion

Implementing a Windows Service with Autofac, Atlas and Quartz.NET is a little bit tricky, as transferring IoC container needs some extra implementation. This sample application can provide a brief overview how to use those libraries in a consolidated manner. Once you are familiar with them, your Windows Service application that needs scheduling will be a lot easier to develop.

References