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.
Autofac
: http://www.nuget.org/packages/AutofacAtlas
: http://www.nuget.org/packages/AtlasQuartz.NET
: http://www.nuget.org/packages/Quartz
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.
Atlas
Preparing Console Application for 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.
internal class Program
{
private static readonly ILog Log = LogManager.GetCurrentClassLogger();
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
: LetAtlas
know thatSampleService
is run as a Windows Service.#2
: LetAtlas
allow to run multiple instances. Comment or remove this, if not necessary.#3
: Register the IoC container built withAutofac
#4
: Add arguements, if provided.#5
: StartAtlas
.
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
.
SampleService
Implementing 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.
internal class SampleService : IAmAHostedProcess
{
private static readonly ILog Log = LogManager.GetCurrentClassLogger();
public IScheduler Scheduler { get; set; } // #1
public IJobFactory JobFactory { get; set; } // #2
public IJobListener JobListener { get; set; } // #3
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");
}
public void Stop()
{
Log.Info("Sample Windows Service stopping");
this.Scheduler.Shutdown();
Log.Info("Sample Windows Service stopped");
}
public void Resume()
{
Log.Info("Sample Windows Service resuming");
this.Scheduler.ResumeAll();
Log.Info("Sample Windows Service resumed");
}
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 aSampleJob
instance.#5
: Lets a trigger to run theSampleJob
instance on the schedule using the cron expression.#6
: Builds a trigger instance.#7
: Let theIScheduler
instance to resolve instances built through the IoC container.#8
: Schedule theSampleJob
instance with the trigger built.#9
: Adds aIJobListener
instance while theSampleJob
is being executed.#10
: Starts theIScheduler
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.
SampleModule
Implementing 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.
internal class SampleModule : Module
{
protected override void Load(ContainerBuilder builder)
{
this.LoadQuartz(builder);
this.LoadServices(builder);
this.LoadLogicLayers(builder);
}
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
}
private void LoadServices(ContainerBuilder builder)
{
builder.RegisterType()
.As()
.PropertiesAutowired(); // #5
}
private void LoadLogicLayers(ContainerBuilder builder)
{
builder.RegisterType()
.As(); // #6
}
}
#1
: Registers theIScheduler
instance.#2
: Registers theIJobFactory
instance.#3
: Registers theIJob
instance. This will resolve theSampleJob
instance.#4
: Registers theIJobListener
instance.#5
: Registers theIAmAHostedProcess
instance. This will resolve theSampleService
instance.#6
: Registers theISampleLogicLayer
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.
SampleJob
Implementing The SampleJob
class actually runs the business logic instance resolved from the IoC container.
internal class SampleJob : IJob
{
private static readonly ILog Log = LogManager.GetCurrentClassLogger();
public ISampleLogicLayer SampleLogicLayer { get; set; }
public void Execute(IJobExecutionContext context)
{
Log.Info("Application executing");
this.SampleLogicLayer.Run();
Log.Info("Application executed");
}
}
And the SampleJob
needs the ISampleLogicLayer
instance.
internal interface ISampleLogicLayer : IDisposable
{
void Run();
}
internal class SampleLogicLayer : ISampleLogicLayer
{
private static readonly ILog Log = LogManager.GetCurrentClassLogger();
public void Run()
{
Log.Info("This has been run");
}
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.
SampleJobFactory
Implementing 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.
internal class SampleJobFactory : IJobFactory
{
private readonly IContainer \_container;
public SampleJobFactory(IContainer container)
{
if (container == null)
throw new ArgumentNullException("container");
this.\_container = container;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
if (bundle == null)
throw new ArgumentNullException("bundle");
return (IJob)this.\_container.Resolve(bundle.JobDetail.JobType); // #1
}
public void ReturnJob(IJob job)
{
}
}
#1
: Returns the resolved job instance. In our example, it returns theSampleJob
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.
SampleJobListener
Implementing SampleJobListener
makes sure the job instance gets all necessary instances injected before executing the job and disposes all relevant resources after being executed.
internal class SampleJobListener : IJobListener
{
private readonly IContainerProvider \_provider;
private IUnitOfWorkContainer \_container;
public SampleJobListener(IContainerProvider provider)
{
if (provider == null)
throw new ArgumentNullException("provider");
this.\_provider = provider;
this.Name = "SampleJobListener";
}
public string Name { get; private set; }
public void JobToBeExecuted(IJobExecutionContext context)
{
this.\_container = this.\_provider.CreateUnitOfWork();
this.\_container.InjectUnsetProperties(context.JobInstance);
}
public void JobExecutionVetoed(IJobExecutionContext context)
{
}
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.