Simple task Scheduling using Global asax

A frequent requirement for ASP.NET developers is to schedule tasks at regular intervals. This can include site maintenance tasks, like cleaning up old files, emailing newsletters on a schedule etc. This article examines one easy option for managing tasks like these without having to configure external tools, and discusses a couple of alternatives.

If you have access to the web server itself, you will probably use WMI to schedule tasks (http://msdn.microsoft.com/en-us/library/aa394601(VS.85).aspx) or you might even consider using Sql Server Agent to run Jobs on a schedule (http://msdn.microsoft.com/en-us/library/ms191439.aspx). But if you are using a shared hosting plan, or Sql Server Agent is not available, you need another way to manage tasks. The Global.asax file gives you access to regularly occurring events that you can use to manage this. The two main events of interest are Application_Start(), which happens when the first request to an application is made - usually as a result of the site first being deployed, or when the application process is restarted for any reason (server shutdown, recompilation etc) and Session_Start() which happens whenever a new visitor lands on your web site. See ASP.NET Application Life Cycle Overview for IIS 5.0 and 6.0 for more details.

If your web application does not feature a file called global.asax, it is easy to add one. Right click on the Project name in Solution Explorer, and choose Add New Item

From the dialogue box that appears, select Global Application Class

Your IDE should open up Global.asax as a result of this, and you should see autogenerated code within a <script runat="server"> block. Five event handlers are ready and waiting which cover Application Start, Application End, Application Error, Session Start and Session End. For the purposes of this illustration, I am going to assume that the regular task will be one that checks a folder for files that are more than one day old, and deletes them. I'm also going to assume that the web application gets enough visitors that Session_Start() will be fired often enough to serve the purpose of trigger point. We need some code that will iterate the files in a folder, and check their created date. If that date is more than 24 hours old, the file gets deleted. We don't want this code to run when every visitor lands on the site. Once a day will do. So, there needs to be some way to check when the task last ran.

At the top of the Global.asax, we will add a variable that holds a DateTime indicating when the task last ran, and initialise it to the current time when the application starts:


private static DateTime whenTaskLastRan;

void Application_Start(object sender, EventArgs e)
{
    whenTaskLastRan = DateTime.Now;
}

We have decided that the task will run in Session_Start(). so we add a call to a method there:


void Session_Start(object sender, EventArgs e)
{
  DoTask();
}

At the bottom of Global.asax, we add the method DoTask() with its implementation code:


static void DoTask()
{
  var oneDayAgo = DateTime.Now.AddDays(-1);
  if (whenTaskLastRan.IsOlderThan(oneDayAgo))
  {
    var path = HttpContext.Current.Server.MapPath("Uploads");
    var folder = new DirectoryInfo(path);
    FileInfo[] files = folder.GetFiles();
    foreach (var file in files)
    {
      if(file.CreationTime.IsOlderThan(oneDayAgo))
      {
        File.Delete(path + file.Name);
      }
    }
    whenTaskLastRan = DateTime.Now;
  }
}

A DateTime - oneDayAgo is set to 24 hours before now, or one day ago. The application variable which was set in the Application_Start() event is compared to oneDayAgo to see which is older. You might raise an eybrow at the method used to do this: IsOlderThan(). It's an extension method which looks like this:


public static bool IsOlderThan(this DateTime dt1, DateTime dt2)
{
  return dt1 < dt2;
}

In itself, it doesn't do much - it just returns a bool indicating whether one DateTime precedes another. However, its use makes the code more readable.

Alternatives

There is one alternative that I have seen for scheduling tasks without the use of third party applications, and that is the use of the Cache object. It is detailed here: http://www.codeproject.com/KB/aspnet/ASPNETService.aspx. For tasks that need to be run more often that visitors land on your site, this may be a better choice. However, it seems a little bit overkill to me. I can't think of a real world scenario where a web application really would need to execute tasks more frequently than visitors land on the site. One place where it may be useful is in performing a task at a particular time. If you need to control the time quite precisely (say with a tolerance of around 2 minutes either way) the Cache approach would be better than hoping that a visitor lands on your site at the right time. Of course ,that depends on your site and traffic levels.

Another alternative is to place the code for DoTask() in the Page_Load event of your home page. This will ensure it is run every time anyone lands on that page. In the case of my site, the home page is one of the least visited pages. You can ensure it fires for every page request by creating a BasePage which inherits from Page, and then subclass all of your web forms from BasePage. DoTask() can be placed in the Pre_Init() event of BasePage. A final option is to stick with Global.asax, and put DoTask() in Application_BeginRequest(). This will also be fired every time a page is requested on your site.

This article has looked at a number of options for automating tasks without having to configure third party tools on your web server. Which option you choose will depend on the frequency of the task, the tolerance levels you can accept in terms of timing the task, and the number of visitors your site gets.