Scheduled Tasks In ASP.NET With Quartz.Net

A perennial question on the ASP.NET forums concerns how to schedule regular tasks as part of a web application. Typically, the requirement is to send emails once every 24 hours at a particular time each day, but it could actually be anything from tweeting on a schedule to performing maintenance tasks. Equally typically, half a dozen members on the forum dive in with recommendations to install Windows Services or schedule batch files with the Task Scheduler - regardless of the fact that most web site owners are not afforded such privileges as part of their shared hosting plan.

I've looked at how you can use the Session_Start event in Global.asax to manage rudimentary "timed" jobs previously. However, it relies on there being sufficient traffic to your web site that the event gets fired often enough for your scheduling needs. And of course, if that is not the case, it is of no use when dealing with time-critical tasks. A much more robust solution can be found in the shape of Quartz.NET - an open source scheduling library which is available via nuget. This article looks at a basic implementation that will get you up and running with a scheduled email job. I have chosen to use a Web Forms application to illustrate the use of Quartz.NET in an ASP.NET setting, but the steps are easily translated to MVC or Web Pages.

The easiest way to include Quartz.NET in your application is via Nuget. You can do this either by typing Install-Package Quartz at the Package Manager Console prompt or by right clicking on the project in Visual Studio's Solution Explorer and selecting Manage Nuget Packages.

Quartz

Note: If using WebMatrix (ditch it!), just click on the Nuget button in the ribbon bar and ignore the error messages

Then search for 'quartz' and click Install when you find Quartz.NET.

Quartz

At its simplest, Quartz consists of 3 primary components - a job, a trigger and a scheduler. A job is the task to be performed. The trigger dictates how and when the job is executed. Together, the job and the trigger are registered with the scheduler, which takes care of ensuring that the job is performed on the schedule dictated by the trigger configuration.

A job is a class. For it to work with Quartz, it must implement the Quartz IJob interface which has one member: the Execute method. This method defines the actions to be performed. Here's an example of a job:

using Quartz;
using System;
using System.Net;
using System.Net.Mail;

namespace ScheduledTaskExample.ScheduledTasks
{
    public class EmailJob : IJob
    {
        public void Execute(IJobExecutionContext context)
        {
            using (var message = new MailMessage("user@gmail.com", "user@live.co.uk"))
            {
                message.Subject = "Test";
                message.Body = "Test at " + DateTime.Now;
                using (SmtpClient client = new SmtpClient
                {
                    EnableSsl = true,
                    Host = "smtp.gmail.com",
                    Port = 587,
                    Credentials = new NetworkCredential("user@gmail.com", "password")
                })
                {
                    client.Send(message);
                }
            }
        }
    }
}

The Execute method takes an IJobExecutionContext object as a parameter. The scheduler passes that in when calling the job's Execute method. It contains configuration data about the job (which you set a bit later). In this simple example, no use is made of the data in the context. All this example does in fact is to send an email message. The method body could contain anything that you want to happen. It could query a database and send emails to all the recipients found there, for instance. But on its own, the job does nothing.

The next section of code illustrates the scheduler being set up and the job, together with its trigger being created and assigned:

using Quartz;
using Quartz.Impl;
using System;

namespace ScheduledTaskExample.ScheduledTasks
{
    public class JobScheduler
    {
        public static void Start()
        {
            IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
            scheduler.Start();

            IJobDetail job = JobBuilder.Create<EmailJob>().Build();

            ITrigger trigger = TriggerBuilder.Create()
                .WithDailyTimeIntervalSchedule
                  (s =>
                     s.WithIntervalInHours(24)
                    .OnEveryDay()
                    .StartingDailyAt(TimeOfDay.HourAndMinuteOfDay(0, 0))
                  )
                .Build();

            scheduler.ScheduleJob(job, trigger);
        }
    }
}

I've called the class in which this takes place JobScheduler, but it could be called anything. In fact, this code doesn't even need to be in its own class. It can be placed wherever you want to start the scheduler. Typically in a web application, that will be in the Application_Start event of Global.asax. In this example, the actual code has been placed in a method called Start (although that too can be named anything). A scheduler is created and started in the first two lines. Then a job is created using the Quartz.NET JobBuilder.Create<T> method, where T is the type of job to be created. In this case, it's an instance of the EmailJob previously defined.

Next, a trigger is created. As I mentioned earlier, the trigger defines when the job is to be executed. In this case, the schedule is specifed as starting daily at midnight and having an interval of 24 hours. It is to execute every day. The options for trigger schedules are extremely flexible allowing for scheduling to a calendar as well as regular time-based intervals. Here's another example of a trigger taken from the documentation:

ITrigger trigger = TriggerBuilder.Create()
    .WithIdentity("trigger1", "group1")
    .StartNow()
    .WithSimpleSchedule(x => x
        .WithIntervalInSeconds(10)
        .RepeatForever())
    .Build();

This trigger has been given a name and group so that it can more easily be identified if you need to refer to it programmatically. It is designated to start immediately an to fire every 10 seconds until the end of time (or the scheduler stops for some reason). The fluent API makes it pretty simple to create triggers and to understand what they are doing.

Finally, the job together with its trigger is registered with the scheduler. All that's needed now is to get this code to execute. As mentioned previously, the best place to do this is within the Application_Start event of the Global.asax file, so a call to the JobScheduler.Start method is added their alongside the other bootstrapping tasks that need to take place on application startup:

using ScheduledTaskExample.ScheduledTasks;
using System;
using System.Web;
using System.Web.Optimization;
using System.Web.Routing;

namespace ScheduledTaskExample
{
    public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            // Code that runs on application startup
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            JobScheduler.Start();
        }
    }
}

If you need to run tasks on a schedule as part of your ASP.NET application with reliable accuracy and don't have access to various scheduling options directly on the server - or even want an alternative to messing around with the Windows task scheduler - Quartz.NET is a great solution.

Date Posted: Thursday, August 7, 2014 8:34 AM
Last Updated: Wednesday, October 22, 2014 9:53 PM
Posted by: Mikesdotnetting
Total Views to date: 27141

24 Comments

Saturday, August 9, 2014 8:12 AM - Tasos Piotopoulos

Great post! What about the application pool's idle timeout? Most shared hosting providers set it at a few minutes. Do those scheduled tasks still work even if none requests a page during the day?

Saturday, August 9, 2014 9:09 AM - Mike

@Tasos,

Nothing in the site will run if the app pool has been unloaded. You can add a job to your scheduler that uses WebClient to request your home page every so often to prevent the app pool idle timeout kicking in.

Wednesday, August 13, 2014 10:21 AM - rahul mehta

very helpful

Thursday, August 14, 2014 6:02 AM - mahmoud

Great! But as mike said, this scheduler has no garantee to run the tasks on scheduled time. If so, what is the difference between using this component and internal Timer class?

Thursday, August 14, 2014 7:34 AM - Mike

@Mahmoud,

As I explained to Mike, you can add a job to the scheduler to "poke" your web application which will prevent your app pool from going idle. That will keep the scheduler going.

The timer class is only fit for very basic schedules. You can't use it to run a task every Sunday, for example, or every two hours between 8.00am and 6.00pm on weekdays only. Quartz provides a huge range of flexibility in scheduling.

Friday, August 15, 2014 10:41 AM - Deepak Dwivedi

Hey Mike,

It is really a very nice article but I have some doubts.
You are saying to add a job to the scheduler and use WebClient to request homepage, but I think I will consume huge bandwidth to ping home page every time.

Also what is the advantage to using Quartz.Net Job Scheduling over Sql Server Job Scheduling.

Thank you.

Friday, August 15, 2014 11:31 AM - Mike

@Deepak,

You only need to request your homepage often enough to prevent the idle timeout from kicking in. By default, that's once every 20 minutes. If your hosting company has set it to a smaller frequency, then the request will need to be made more frequently. Even if you set it for once a minute, that's only 60 requests an hour. That is not considered huge in anyone's book.

Some shared hosting plans don't provide access to SQL Server scheduling. Or SQL Server might not be part of your package if you are using Access or SQL Compact for your database. In those cases, SQL Server Jobs are not an option.

SQL Server Express is a popular option for low cost shared hosting. But it doesn't include the SQL Server Agent, so again, SQL Jobs are not possible.

Tuesday, August 19, 2014 4:05 PM - Marko Lahma

@Deepak

What might be worth noting is that Quartz.NET also supports database-backed job store. Say that trigger misses its scheduled fire time, the scheduler can recover from this. Scheduler sees that the scheduled fire time has passed and misfire handling kicks in. In other words, persisted schedules can be tracked and misfired triggers can be acted upon. See more details in docs about misfires.

When creating site ping functionality you can always define an 'echo page' that return only minimum text/plain OK response. Not a bandwidth hog then.

Wednesday, August 27, 2014 9:53 PM - jorge

ng System.Web.Optimization;
using System.Web.Routing;
using MTF.Classes;

namespace MTF
{
public class Global : System.Web.HttpApplication
{

protected void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);

JobScheduler.Start();
}

I have an error with these two lines:
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);

Visual is not recognizing the commands. am i missing any library?
Thanks

Wednesday, September 3, 2014 1:22 PM - Satyabrata

Nice one.Very helpful.

Friday, September 5, 2014 8:04 PM - Phil Boyd

RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);

RouteConfig and BundleConfig are 2 classes created within the basic MVC application template. Look for them in the App_Start folder.

Tuesday, November 11, 2014 8:30 AM - BG

Great post!
Can you elaborate on Quartz.NET vs async/await-functionality in C# 5.0.
Thanks

Tuesday, November 11, 2014 9:18 AM - Mike

@BG,

They have nothing to do with each other.

Tuesday, November 11, 2014 1:24 PM - FRK

How about using quartz.net to perform always running job with an interval from a windows service?

Is quartz.net designed to be used specifically for ASP.NET only or it can work with no issues in windows services as well?

Tuesday, November 11, 2014 8:08 PM - Mike

@FRK,

Sure, you can use Quartz.NET in combination with a Windows service. I was working on just such a project when I first came across Quartz.NET.

Thursday, November 27, 2014 5:30 AM - Manas

Hi Mike,

Thank you for awesome article.

My concern is it might impact website
performance.

Because if we use Windows Service or Scheduler, this is different thread. It doesn't affect website performance.

So please suggest.
Regards,
Manas

Thursday, November 27, 2014 11:43 AM - Mike

@Manas

If you can use a Windows Service or Scheduler, then do so. This article is for those people who can't.

I haven't measured the performance of the component, but if it was a concern for me, then I would.

Sunday, November 30, 2014 7:18 PM - Mohammed Kalloub

Hi Mike,
Thank you for your useful article.

I implement it in my project, and everything run as expected.

but when I published the project on the web server, Quartz.net not executed, why?!!

Sunday, November 30, 2014 9:05 PM - Mike

@Mohammed

Based on the information provided, I suspect something went wrong.

Monday, December 1, 2014 11:41 PM - Mohammed

Hi Mike,
Thank you for your reply.

I think that done because of I'm using a shared web hosting plan (Reseller Plan).

Is that possible?!, or what is the reason for the failure!!

Note: Quartz run locally & on local server with no error.

Tuesday, December 2, 2014 8:12 AM - Mike

@Mohammed

I have no idea. You should really post your issue to a forum somewhere.

Wednesday, December 3, 2014 1:42 AM - Huy

Great topic, but how to stop running job by click event of button on web page?

Tuesday, December 9, 2014 11:12 AM - sarta

Hi, I want to know does quartz is free or not

Wednesday, December 10, 2014 5:13 PM - Mike

@sarta,

Yes, it's free.
Add your comment

If you have any comments to make about this article, please use this form to do so. Make sure that your comment relates specifically to the article above. More general comments can be posted through the form on the Contact page.

Please note, all comments are moderated, and some may not be published. The kind of things that will ensure your comment is deleted without ever seeing the light of day are as follows:

  • Not relevant to the article
  • Gratuitous links to your own site or product
  • Anything abusive or libellous
  • Spam
  • Anything in a language I don't understand including gibberish.

I do not pass email addresses on to spammers, so a valid one will assist me in responding to you personally if required.

Recent Comments

Allen Michaels 12/17/2014 4:37 PM
In response to Cascading DropDownLists with jQuery and ASP.NET
Fantastic thank you so much!...

Emily 12/17/2014 12:36 PM
In response to Parameterized IN clauses with ADO.NET and LINQ
Thanks, very helpful!!!! Can i use this for multiple in's ????? IN (.....) and IN(...) and IN...

sss 12/16/2014 3:06 PM
In response to Solving the Operation Must Use An Updateable Query error
good...

Gjuro 12/15/2014 10:30 PM
In response to Examining the Edit Methods and Edit View
You have one fromr (and it should be from, I suppose). :-)...

Gjuro 12/15/2014 10:27 PM
In response to Adding Search
Hi, thnx for all this C#->VB translations. Yet, the following code block is (slightly) in error it a...

Scot 12/14/2014 1:39 PM
In response to Entity Framework 6 Recipe - Alphabetical Paging In ASP.NET MVC
Thanks,Mike I found solution....

Gjuro 12/13/2014 10:52 PM
In response to Accessing Your Model's Data from a Controller
The article mentions "Creating an Entity Framework Data Model for an ASP.NET MVC Application" (at is...

Samuel 12/13/2014 8:40 AM
In response to Displaying The First n Characters Of Text
I have failed to use the extension because it throws an error that it doesn't recognise the chop be...

Ignas 12/12/2014 5:11 PM
In response to Cleaner Conditional HTML Attributes In Razor Web Pages
Any suggestions for Html Helper elements with HtmlAttributes, when you need to conditionally set it...

Gautam 12/11/2014 8:50 PM
In response to Validation In Razor Web Pages 2
Hi Mike Is this required for V3, non html helper input...