A couple caveats before I go into all of this:

  1. I’m using Visual Studio 2012
  2. I barely know what I’m doing with Autofac
  3. I barely know what I’m doing with Quartz
  4. I barely know what I’m doing with Azure Worker Roles

Okay, now that that is out of the way I’ll get to the meat of this post. Basically, I am working on an Azure web application and I have a bunch of different jobs I need to execute at various intervals. I have one that processes and email queue (not using Azure Queues). I have another that purges an event log in an Azure SQL database. I have a few others that take anywhere from 40-80 minutes to run really long processes on the same database in order to prepare a bunch of data for faster reporting.

I tried to use the Azure Web Jobs idea but creating little command line applications and then getting them deployed just kind of a sucked from a workflow process. Maybe there is an easier way to get them deployed but basically I had to build them, create a zip file of the release directory, and then upload that using the Azure Management Portal. That sucked. This Guy makes them sound wonderful and, maybe they are, but I couldn’t do the handy “New Azure Webjob Project” in Visual Studio 2012. Yeah, I tried the Visual Studio Extension which claims to work with VS 2012 but it doesn’t. So, in the end, I went in a big circle and ended up back with an Azure Worker Role.

I already figured out how to deploy the worker role automatically using TeamCity so now I just had to build the role and get it to start scheduling jobs. That’s where this post comes into play. In it I’m basically going to walk you through the construction of my entire project so far. There will be large swaths of code. Feel free to use whatever you want or suggest improvements if you’d prefer.

The Projects

After creating a worker role project in visual studio you end up with two projects. One is the configuration for the Azure stuff (in this screenshot it is the one titled Aemis.Scheduler). The second is the actual worker role (Aemis.SchedulerService) within which you can see a class called WorkerRole. Within WorkerRole are a few different method which will have to add some logic to in order to get Autofac and Quartz configured correctly.

I basically didn’t touch the Run method. It calls a secondary method RunAsync which I also left alone. Basically, those two things just keep the worker role alive so there was no reason for me to mess with them. The other two methods, OnStart and OnStop however are crucial to getting the Autofac and Quartz stuff to work properly.

OnStart is sort of like the Global.asax.cs method of Application_Start in a .NET MVC project. This method is executed as soon as the Worker Role is started so it is where you can initialize things.

In my OnStart I push most of the heavy lifting into some other methods. Here is my `OnStart:

public override bool OnStart()
{
    IContainer container = AutofacConfig.Register();
    ConfigureScheduler(container);
    // Set the maximum number of concurrent connections
    ServicePointManager.DefaultConnectionLimit = 12;
    return base.OnStart();
}

Once AutofacConfig.Register fires off all of the Autofac stuff is done. I’ll revisit that in a bit. For the time being just know that Autofac knows about all of my Jobs and every other dependency and can properly inject things as they are needed. The Quartz integration is, for the most part, accomplished with Autofac.Extras.Quartz.

Scheduling

I looked into Hangfire and Quartz.NET and, frankly, Quartz.NET seemed easier to me. It doesn’t offer all the same cool power features that Hangfire does but I think it will suffice for my needs.

One of the things I like about Quartz was the relative simplicity of configuring the job schedule and trigger. I am still at a very basic level usage here so I haven’t gotten to the point where I’m storing configuration information in the database. Instead, each job I create (extending IJob) has a public static method called Configure that is responsible for providing all the schedule information for that job. Here is an example:

public new static void Configure(IScheduler sch)
{
    IJobDetail job = JobBuilder.Create<PurgeEventLog>()
        .WithIdentity("PurgeEventLog", "admin")
        .Build();

    ITrigger trigger = TriggerBuilder.Create()
        .WithIdentity("PurgeEventLogTrigger", "admin")
        .WithSimpleSchedule(x => x
        .WithIntervalInSeconds(10)
        .WithInterval(new TimeSpan(1, 0, 0, 0, 0)) //one day
        .RepeatForever())
        .StartAt(new DateTimeOffset(2016, 1, 1, 22, 0, 0, new TimeSpan(0, 0, 0))) // starting at 10pm
        .Build();

    sch.ScheduleJob(job, trigger);
}

This job just runs once a day at 10pm each day. Pretty simple to read and understand. The Identity stuff let’s you “group” your job and trigger; mine are in the “admin” group. But, honestly, I don’t know what that group stuff does yet so I just put them all in the same group.

I really like having the configuration information for the job set within the Job class; it keeps all of the info in one easy to find place. However, I will be moving the configure logic into the database at some point so that it can be modified via a different application. However, when using in memory configuration I really like this approach.

After defining my jobs and giving them each a Configure method I do a little Reflection in order to dynamically configure each with Quartz. This happens in my ConfigureScheduler method:

private void ConfigureScheduler(IContainer container)
{
    scheduler = container.Resolve<IScheduler>();
    IEnumerable<Type> jobs = JobLoader.GetJobs();

    foreach(var job in jobs){
        Logger.Info(job.Name.ToString() + " configuring...");
        job.GetMethod("Configure", BindingFlags.Public | BindingFlags.Static).Invoke(null, new object[] { scheduler });
    }

    scheduler.Start();
}

There may be a better way to do all of this. But I didn’t want to have to remember to edit the WorkerRole class every time I added a new Job to the project. Plus, this only gets executed one time so performance isn’t much of a consideration.

Here is the JobLoader.GetJobs method which finds all of the jobs via reflection:

public static IEnumerable<Type> GetJobs()
{
    string nameSpace = "Aemis.SchedulerService.Jobs";
    return Assembly.GetExecutingAssembly().GetTypes().Where(t =>
           String.Equals(t.Namespace, nameSpace, StringComparison.Ordinal)
           && t.GetInterface("IJob") == typeof(IJob)
           && t.BaseType == typeof(BaseJob)
           && !t.IsInterface
           ).OrderBy(t => t.Name);
}

In your case you might just want to look for things of type IJob (that’s a Quartz defined interface). In my case I Have a BaseJob class that I always extend so, for extra safety, I made sure I only grabbed classes that extended it as well.

Basically, once my ConfigureScheduler method finishes Quartz is completely initialized with every job and it’s accompanying triggers and, I’m done with Quartz. We’ll look at a job in a bit just for clarity but it’s contents have very little to do with Quartz beyond fulfilling the contract of the IJob interface.

Autofac

I break my Autfac stuff out into modules. I like the cleanliness of it. For the sake of clarity I’ll show you all of my Autofac modules for this solution. I actually have a couple more projects that I didn’t include in the earlier screen shot; Aemis.Logging, Aemis.Communication, and Aemis.Data.

Here is the main AutofacConfig.Register method that I called from WorkerRole.OnStart

public static IContainer Register()
{
    var builder = new ContainerBuilder();

    // register the logging module
    builder.RegisterModule(new AutoFacLoggingModule());

    // Register the data module for database access
    builder.RegisterModule(new AutoFacDataModule()
    {
        ConnectionString = CloudConfigurationManager.GetSetting("AemisConnection").ToString(),
    });

    // Register the communications module
    builder.RegisterModule(new AutoFacCommunictionsModule()
    {
        slackWebhookURL = CloudConfigurationManager.GetSetting("slackWebhookURL").ToString()
    });

    // Register our jobs
    builder.RegisterModule(new AutoFacJobModule());

    // build an IContainer
    return builder.Build();
}

Not a ton to see there. Note that I’m initializing some properties of two of the modules by loading from the CloudConfigurationManager. Using the CloudConfigurationManager makes sure that the values are loaded properly both locally during development as well as once I am in Azure hosting. I could have some error handling but I really want the exception to be thrown if I forget to set either of those properties in the Role configuration.

The AutofacLoggingModule is lifted directly from the AutoFac documentation on incorporating log4net so I’m going to skip that here and get right into the Data module.

Data Module

The Data Module is built on top of NPoco. I have some custom classes in there that I won’t be getting into but seeing how everything is registered might be helpful to some folks so here is the Autofac registration:

public class AutoFacDataModule : Module
{
    public string ConnectionString { get; set; }

    public AutoFacDataModule()
    {
        // Set defaults
        ConnectionString = "DefaultConnectionString";
    }

    protected override void Load(ContainerBuilder builder)
    {
        base.Load(builder);

        // Initialize the database connection factory
        var dbFactory = CustomDatabaseFactory.Setup(ConnectionString);

        // Register the database type to create from the factory
        builder.Register(c => dbFactory.GetDatabase() as CustomDatabase)
             .As<IDatabase>()
             .AsSelf()
             .InstancePerLifetimeScope()
             .OnActivating(d => d.Instance.BeginTransaction())
             .OnRelease(d => d.CompleteTransaction());

        // Register all the repositories
        builder.RegisterAssemblyTypes(typeof(CustomDatabase).Assembly)
            .Where(t => t.Name.EndsWith("Repository"))
            .InstancePerLifetimeScope();

        // Register all the loaders
        builder.RegisterAssemblyTypes(typeof(CustomDatabase).Assembly)
            .Where(t => t.Name.EndsWith("Loader"))
            .InstancePerLifetimeScope();

    }
}

The Repository classes are those that actually read/write to the database. The Loader ones are helper libraries that assist with One to Many relationships.

Communications Module

One of the things I decided to do with my jobs; at least while I get a good feel for how all of this works is to use Slack in conjunction with them and, each time a job fires off, to post a message to a slack channel. That is part of why all of my Jobs have a base class so they can all communicate to Slack the same way at the same time in their execution cycle.

Registering my communications module is pretty straight forward:

public class AutoFacCommunictionsModule : Module
{
    public string slackWebhookURL { get; set; }

    public AutoFacCommunictionsModule()
    {
    }

    protected override void Load(ContainerBuilder builder)
    {
        base.Load(builder);

        var slackClient = new SlackClient(slackWebhookURL);
        builder.RegisterInstance(slackClient).As<SlackClient>();

    }
}

I needed to make sure sure the web hook url was injected into the SlackClient and this is the solution I managed to get working. I am not sure if there is a better way to do this but this, at least, worked. If there is a better practice please let me know.

Jobs Module

This section is the most relevant to the title of the post. This hooks up Quartz with Autofac.

Because of the way I defined my Jobs I need to have Autofac autowire dependencies by property. The Autofac.Extras.Quartz library, as of the day I wrote this, didn’t support Autowiring properties, so I had to extract some of the source code of that library to get my stuff working. The developer of the AutoFac.Extras.Quartz library has indicated he will be adding optional support for this in the next release so you may not need my code exactly at this point.

public class AutoFacJobModule : Autofac.Module
{
    public AutoFacJobModule()
    {
    }

    protected override void Load(ContainerBuilder builder)
    {
        base.Load(builder);
        builder.RegisterModule(new QuartzAutofacFactoryModule());

        /* extracted this registration from:
               QuartzAutofacJobsModule
         * https://github.com/alphacloud/Autofac.Extras.Quartz/blob/master/src/Autofac.Extras.Quartz/QuartzAutofacJobsModule.cs
           so that I could add PropertiesAutowired
        */

        Assembly assemblyToScan = typeof(AutoFacJobModule).Assembly;

        builder.RegisterAssemblyTypes(assemblyToScan)
          .Where(type => !type.IsAbstract && typeof(IJob).IsAssignableFrom(type))
          .PropertiesAutowired() // I needed this!
          .AsSelf().InstancePerLifetimeScope();


        /*
        If you didn't need to do the autowire you could have just used this which is where I got my logic from in the first place:

         builder.RegisterModule(new QuartzAutofacJobsModule(typeof (AutoFacJobModule).Assembly));

        */

        /*
        In the newer version of Autofac.Extras.Quartz you'd be able to auto-wire dependencies like so:
        builder.RegisterModule(new QuartzAutofacJobsModule(typeof (AutoFacJobModule).Assembly)
        {
            AutoWireProperties  = true,
            PropertyWiringOptions = Autofac.PropertyWiringOptions.AllowCircularDependencies

            // see http://autofac.org/apidoc/html/33ED0D92.htm for other options
        });



        */

    }
}

That’s all of my Autofac stuff. At this point when Quartz invokes a job it gets the job from Autofac and Autofac makes sure all of my dependencies are created (db files and SlackClient) and then my Job can execute.

Quartz Job example

Here is an example of one of my jobs. It calls a stored procedure on the database which takes about 1.5hours to execute. All of my jobs follow this same basic structure:

public void Execute(IJobExecutionContext context)
{
    StartJob();
    int year = System.DateTime.Now.Year;
    BuildReportDataForYearStatus status = reportRepository.BuildReportDataForYear(year);
    EndJob(status.Result);
}

StartJob logs some info to the database about what job is executing and when it started. This uses NPoco which creates a transaction for the entire job Execution pipeline so I have to manually commit the transaction within the scope of StartJob or else, if the job fails, it will never appear in the db. The BaseClass keeps track of the JobLog entry so that EndJob can then update the job with the jobLog.Result and the time the job completed. EndJob is also where the slack communication happens.

Comments