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.

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.

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:

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:

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:

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:

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.

You might also like...

66 Comments

09 August 2014 08:12 - 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?

09 August 2014 09:09 - 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.

13 August 2014 10:21 - rahul mehta

very helpful

14 August 2014 06:02 - 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?

14 August 2014 07:34 - Mike

@Mahmoud,

As I explained to Tasos, 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.

15 August 2014 10:41 - 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.

15 August 2014 11:31 - 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.

19 August 2014 16:05 - 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.

03 September 2014 13:22 - Satyabrata

05 September 2014 20:04 - Phil Boyd

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

11 November 2014 08:30 - BG

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

11 November 2014 09:18 - Mike

@BG,

They have nothing to do with each other.

11 November 2014 13:24 - 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?

11 November 2014 20:08 - 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.

27 November 2014 05:30 - 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

27 November 2014 11:43 - 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.

30 November 2014 19:18 - 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?!!

30 November 2014 21:05 - Mike

@Mohammed

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

01 December 2014 23:41 - 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.

02 December 2014 08:12 - Mike

@Mohammed

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

03 December 2014 01:42 - Huy

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

09 December 2014 11:12 - sarta

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

10 December 2014 17:13 - Mike

@sarta,

Yes, it's free.

19 December 2014 05:15 - Sathish kumar

Hi,
If we change the server time after hosting the Quartz.Net Scheduler in server will it work correctly or it wont trigger?

07 January 2015 05:18 - smruti

I use this quartz application for email send but when i run application there is an error that jobrunshell.cs not found.
I put all debugger .it happen when controller reach at client.send(message).

Plz guide me.

08 January 2015 07:46 - Mike

@smruti

That's not a Quartz issue. Something is going wrong with your email sending. You should check the Exception message for clues to fix it.

12 January 2015 12:38 - Anders Baumann

Hi Mike.
Thanks for a nice article. I am considering using Quartz.Net in a .Net MVC app hosted in Azure or AppHarbor. What happens if my application is restarted/redeployed? (This happens with AppHarbor upon every push to the repository.) I don't want multiple Quartz jobs running in parallel.

12 January 2015 14:32 - Mike

@Anders

Quartz will run in the same process as the web app, so if it is stopped, so is Quartz.

23 January 2015 10:55 - Kadu Portugal

Hi Mike,

Thank you for your useful article.

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

But, in 20 minutes the app pool is idle and stop my send email.
I put in my code this code to execute every minute:

But the webclient not renewed the time of app pool, and again in 20 minutes sending stopped!

What did I do wrong?

Thanks!

28 January 2015 22:06 - Mike

@Kadu,

You need to request a page in your site to keep it alive, not Google.

31 January 2015 19:23 - Ingmar

On December, 1st, Mohammed reported a problem with triggers not being executed when the web application is deployed.

I have the exact same issue.

To go sure I have no side effects, I used Mike's code 1:1 (except for his smtp credentials ;)).

Everyhting works perfectly as long as I run it locally in the Visual Studio IDE.

But once I deploy the application to my server, the trigger does not get fired anymore.

And in my case, it is NOT a shared server. The machine is my own and right under my desk. Windows Server 2012 R2 with IIS 8.5. The relevant application pool is set to StartMode=AlwaysRunning and Idle-Time out=0. In other words, it should not be sleeping.

I was wondering if anybody has/had the same problem and knows the solution. Would be great!!

Thanks!

(and thanks to you Mike for this cool article)

17 February 2015 20:18 - Mike

Mike you just made my life a lot simpler. I have been looking for something like this for years. Great Article. Thanks

24 February 2015 10:43 - Justin

I'm having the same problem as Ingmar. Everything is working fine locally in Visual Studio, but I can't get it to work on Windows Server 2012 R2, not even once.

07 March 2015 07:07 - Satyabrata Mohapatra

Sir,

ASP.NET Web Forms is not part of ASP.NET 5.
http://stephenwalther.com/archive/2015/02/24/top-10-changes-in-asp-net-5-and-mvc-6

Though asp.net mvc gaining popularity day by day,web forms is still popular and many companies still not willing to move across to MVC. It is sad that web forms is no longer is a part of asp.net 5.Whats your thought??

17 March 2015 10:01 - Muhammad Okfriansyah

Nice article here, But i have a question, How to make it dynamically? Like the starting from scheduler and endDate for scheduler can change dynamically from the application. And i already have the email reminder system that user can click the button to reminder(send an email) that person, but it still manual. But how to make it runs using scheduler? Is it to put the jobScheduler.start() in Page_Load? Thanks in advance

28 May 2015 16:56 - cesar

When the application pool recycle then the quartz scheduler is also destroyed, how do we keep it alive what are the best ways to do this.

29 May 2015 09:48 - Mike

@cesar,

If you build the schedule in the Application_Start event of global.asax, then it will get rebuilt each time the application restarts.

27 June 2015 06:20 - Howie

Mike - Great article. I'm curious about the lifetime of the scheduler - I see your JobScheduler.Start() is static and that you call it from Application_Start, but how does the scheduler keep running after the Start() method schedules the jobs? Maybe I am confused about scoping and object lifetime in ASP.NET apps. Additionally, if I want to schedule more jobs once the application has already started, obviously I wouldn't call Start() again since the scheduler was already started (but again, not sure how it is still alive), so following application startup how do I get access to the existing scheduler instance?

27 June 2015 21:36 - Mike

@Howie,

The scheduler instance exists for the lifetime of the application. Each time the application restarts, the scheduler is restarted also.

You will have to check the documentation to establish how to register jobs dynamically with an existing instance. It's not something I have explored.

08 July 2015 12:41 - Noori

how can we add the dll if Nuget not appears.

08 July 2015 21:12 - Mike

@Noori,

You can download it from the project site. Search for Quartz.net.

16 July 2015 08:42 - saket singh

hi Mike, great tutorial on Quartz.net ,
but i have One Problem ,
Everything is working fine as long as i am running my app on Local server ,
but on iis , it's not working .
although i have called a dummy page on Application_End Event.

21 July 2015 21:29 - Max G

Hi, I've opted for this solution in one of my applications but i've found that the apppool is getting reseted, and I loose the trigger execution sometimes, is there a way for quartz to auto detect misfires and track executions, persist or something like that?

21 July 2015 22:11 - Mike

@Max,

When your app pool recycles, the Application_Start event should fire and the scheduler should be restarted. It is more likely that your app suffers from an idle timeout if it isn't very busy. You can either set the timeout value to 0 if you have access to IIS configuration (https://technet.microsoft.com/en-us/library/cc771956%28v=ws.10%29.aspx?f=255&MSPPError=-2147217396) or find out what the idle time out is, and schedule a task to request an "echo" page before the app idles. An "echo" page will only output, say, DateTime.Now so it doesn't consume huge resources, but keeps the site alive.

Another potential solution is HangFire.io. I plan to explore this at some time in the near future an will blog about it.

01 August 2015 15:39 - Arbaz

Hey Mike,
What If I want to set a schedule weekly or every Sunday to run. What would the code for that? I am sorry I am newbie in asp.net c# and on beginners level. Thanks

04 August 2015 08:09 - Mike

07 August 2015 18:23 - E.R.

Can I use Quartz to fire off unscheduled tasks that need to run in the background. I'd like to fire the task off from the web page, the task will update a table with status, and the page that launched the task will check the table for current status/completed.

22 September 2015 14:55 - Muneer

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? I am creating web form application... Thanks In Advance

24 September 2015 10:41 - Mike

@Muneer,

Check the using directives in the last piece of code in the article and compare it to yours. You are probably missing the ones for System.Web.Optimization and System.Web.Routing

24 September 2015 16:45 - Uğur Dinç

Simplest and best explanation on Quartz.NET. Thank you!

15 October 2015 22:53 - Wagner

Great article, thanks! I do have a question:

Say your task is set to run every 24 hours and was scheduled to start at 1 AM in the morning, but the app pool was not running at that time. When the app pool runs again:

1. will the task run immediately or wait until 1 AM the next day?
2. if it runs immediately, when will it run next? 24 hours after the first run, or right at 1 AM as originally scheduled?

Thanks!
Wagner

16 October 2015 07:40 - Mike

@Wagner

When the app pool restarts, if you use the simple approach I outlined above, the task will be rescheduled to run at 1AM every day. It will execute the next time that mile stone is hit.

What you can do is log when a task is run in a database, and when your startup code executes, check to see the last time each task was run and if any were missed, you can choose what to do with them.

16 October 2015 15:09 - Wagner

@Mike

Thanks for the quick response. I've been playing with Quartz for the last day and it's been really simple to implement so far (thanks a lot to your great article!).

18 December 2015 22:15 - Anil

This is really an amazing article!

I am currently looking for similar solution, we have a requirement to run a thread/job on frequent intervals ( may be every 5 mins). There are two scenarios for which I would like to know whether i can go with asp.net scheduling (like using quartz-net) or windows service/scheduled job is better.

1. My code is running on a web farm with 3 nodes
2. I need to perform DB update operations through the recurring job.

Is it possible to use quartz or similar solutions when we have these scenarios are present? Please let me know what I approach I should follow?

Thanks in advance

21 December 2015 07:34 - Mike

@Anil,

If you have access to scheduled jobs on the server, they are always preferred to solutions like Quartz. As I said in the introduction to the article, Quartz is an option when Windows services or scheduled jobs are not available.

21 December 2015 18:39 - Anil

Hello Mike,

Thank you so much for your prompt response!

Still I am curious to know; if we need to perform DB operations through any recurring thread and if we also have more than one web node in the farm, how we can handle these scenarios? I assume the jobs will be running from all the nodes in the farm and there is a possibility that the same job/task can been executed from different nodes and this can cause issues? The example that you provided for sending mail, if we have code running from more than one node, it can trigger more than one mail? Please correct me if I am missing anything.

Thanks Again,
Anil

05 January 2016 13:00 - Trang

Hello Mike,

I'm using quartz for sending emails list, it's similar as your article but I'm afraid of timeout and performance issues when my email lists can over 100 emails. Is there any risk when the job running time takes so long?

07 January 2016 10:02 - Mike

@Trang,

There shouldn't be anything to timeout. The Quartz job is not run in the context of a page (which has an execution timeout).

11 January 2016 21:04 - Misael Alberto Moneró Thompson

12 January 2016 16:28 - manvendra patel

i have a quartz.net scheduler in my webapplication for send automail and it work fine ,fire the job according to my schedule time.

but it does not working on next days on schedule time
can i use below code and where put it ?
WebClient webClient = new WebClient();
Stream stream = webClient.OpenRead("http://www.google.com");

24 February 2016 07:44 - jake

Hi

I'm using quartz.net and follow your guide on how to implement this but I have a problem on firing the job. The start function is called but the execution is not done. I'm simply followed your guide on implementing it and set the interval into 10 seconds to see it working but no luck on that.

Is there any setup should I tweak to make it work? I don't know what I'm missing for I did everything what your guide has made.

24 February 2016 07:57 - Mike

@jake,

I can't guess where you have gone wrong. You should post a question to a forum with more detail about what you did and what goes wrong.

24 February 2016 11:14 - jake

@Mike

I figured it out now where I made a mistake. Your guide is good. Thank you for replying quickly.

Unfortunately, something went wrong and your message or comments have not been submitted
successfully. I'll try to fix whatever the problem is as soon as I can.

Thanks for your comments. They have been successfully sent to me. I will try to respond
if necessary as soon as I can.