How many times have you read about someone wanting to run a regularly occurring task within their web application? If you've been reading the online discourse long enough, then the answer is: a lot. And by “a lot”, I mean it's a daily question.
So what's the answer, you ask? Revalee.
Come again? That's Revalee and it's pronounced like the word reveille, as in “a signal to arise.”
Revalee is Windows Service that you use in conjunction with your web application. In most cases (although this is certainly not a requirement), you would install the Revalee Service on the same server as your web hosting environment (IIS). Next, you use the Revalee client libraries within your ASP.NET application (whether MVC or not) to communicate to Revalee. The best part, Revalee is a free, open-source project.
How does it work? To keep it brief: Revalee (as a Windows Service) listens for callback requests from your web application. (There are authentication and verification steps to make sure no one attempts to spoof your requests too, but this is the 30,000-foot overview so we'll move right along.) Revalee then logs your application's callback request, which in a nutshell can be interpreted as “Hey, Revalee, call my URL back at this date & time.” Later, at the indicated date & time, Revalee calls back your web application just like any web application user might do from their browser. That's it.
What all this means is that you can keep your application's business logic all contained in one place. No more having to split your application's functionality into various chunks, including some hard-to-maintain ones. Some code lives in your web code, some in archaic command line routines used by the task scheduler, and even, some in your database's job scheduler. How is all that maintained? Who configures it? Is all of that code checked into your source code repository? Is the configuration all documented? We have all experienced this. Hopefully, Revalee can help you solve some of this.
For more background information on Revalee and how it works under the hood, take a look at these previous articles: Scheduling tasks with Revalee and MVC and Scheduling tasks with Revalee and MVC (Part 2). Those previous articles focused on scheduling one-time callbacks with Revalee (think: send a future email to a particular user). This article will now focus on scheduling recurring callback requests.
Disclaimer: Revalee is a free, open source project written by the development team that I am a part of. It is available on GitHub and is covered by the MIT License. If you are interested, download it and check it out.
So you have your new ASP.NET MVC application written, it's been tested & deployed, and users are finally making good use of it. Great! However, as expected, the first post-launch enhancement request (aka. Phase 2) arrives mere moments after the application went into production: “We need a high-level, summary report emailed to the business team nightly.” It's not an unreasonable request. It is time, however, to use Revalee.
First of all, let's prep the web server (IIS) to work with Revalee and recurring tasks. To do this, you will need to add some elements to your application's web.config. You'll start by defining a new section (
<revalee>) in the configuration file:
type="Revalee.Client.Configuration.RevaleeSection" requirePermission="false" />
Next, you will add the contents of the new
<revalee> configuration section. The details included in this configuration section define where Revalee is installed, how it is configured, and what the details of your recurring task (or tasks) will be:
<task periodicity="daily" hour="05"
minute="00" url="/Report/DailySummary" />
Finally, you include a custom module that will make use of the configuration information listed above, known as the
Based on the
<task> element listed in the example code above, your web application will now self-register a recurring Revalee callback to
http://yourwebapp.com/Report/DailySummary every day at 5:00 AM. Simply put, this means that your MVC application's
ReportController will be running its
DailySummary() method every day at 5:00 AM. All of the business logic has now been funneled into the
DailySummary() method. That's it: no fuss, no muss.
Under the Hood
So how does the
RecurringTaskModule work? Rendered to its simplest form, this
IHttpModule implementing class operates as follows:
- IIS launches the web application and loads the
RecurringTaskModule, which is a class implementing the
- Next, the
LoadManifest() method reads the configured details defined in the
<revalee> section of the
- After validating the configuration, a “heartbeat” callback is scheduled with Revalee to run at
- The Revalee (Windows) Service receives and performs the “heartbeat” callback immediately.
BeginRequest event is triggered and the incoming “heartbeat” callback (
HttpRequest) is analyzed.
- The “heartbeat”
HttpRequest triggers all recurring tasks (loaded from the web.config) to be scheduled with the Revalee Service.
- The module waits to process future, incoming recurring task requests.
If the analysis indicates that the
HttpRequest is, in fact, a recurring task, then the request is intercepted (that is,
HttpApplication.CompleteRequest() is called); otherwise, the request continues on through the normal
HttpRequest processing pipeline.
You may be wondering: why are recurring tasks scheduled every time the web application loads (and if you weren't wondering about that, you are now)? Won't this result in a recurring task being scheduled multiple times? The simple answer is: yes. (And that's OK.) When the
RecurringTaskModule receives an
HttpRequest for a recurring task, it uses the first such request received to process the recurring task and then ignores all other instances of incoming requests for the same recurring task. To identify duplicate tasks, each task is identified by a hash computed from its constituent properties. Thus, two tasks cannot share the exact same details, namely, periodicity, hour offset, minute offset, and callback URL.
Generating your own
IHttpModule may be something that you need to do for a future project, so let's delve into the handling of the
BeginRequest event (just one aspect of the
IHttpModule to be sure, but not an insignificant one):
private void context_BeginRequest(object sender, EventArgs e)
HttpApplication application = sender as HttpApplication;
if (application != null && application.Context != null && application.Request != null && _Manifest != null)
HttpRequest request = application.Request;
RequestAnalysis analysis = _Manifest.AnalyzeRequest(request);
if (_Manifest.TryGetTask(analysis.TaskIdentifier, out taskConfig))
if (RevaleeRegistrar.ValidateCallback(new HttpRequestWrapper(request)))
application.Context.Response.StatusCode = (int)HttpStatusCode.OK;
application.Context.Response.SuppressContent = true;
AnalyzeRequest() method (more on this later) determines whether (or not) an incoming
HttpRequest is a recurring task. If it is recurring, the
SetLastOccurrence() method is the final “gatekeeper” determining whether not an incoming recurring task request should be ultimately processed or ignored (because a duplicate request of this particular recurring task was already processed).
Recurring Task Periodicity
What levels of recurrence does Revalee support, you ask? Hourly and daily.
An hourly recurring task is defined as follows:
|minute||Value between 0 and 59 (inclusive)|
|url||Url of the callback target|
<task periodicity="hourly" minute="45" url="/Report/HourlyUpdate" />
A daily recurring task is defined as follows:
|hour||Value between 0 and 23 (inclusive) [24-hour format]|
|minute||Value between 0 and 59 (inclusive)|
|url||Url of the callback target|
<task periodicity="daily" hour="18" minute="15" url="/Report/DailySummary" />
Other levels of recurrence can be achieved by including additional
<task> elements (for sub-hour recurrence) and/or custom processing at the
Controller.Action() level (for super-daily recurrence). For example, to only process the requests on every Friday at 6:15 PM you might include the following code in your
public ActionResult DailySummary()
if (DateTime.Now.DayOfWeek == DayOfWeek.Friday)
return new HttpStatusCodeResult(HttpStatusCode.OK);
So how do you prevent the processing of every, single incoming
HttpRequest from bogging down your web application? Keep it fast and simple for the vast majority of requests. In this case,
String.StartsWith() using the
Ordinal string comparison is the gatekeeper. This will
return false as soon as the first character does not match the value of
_RecurringTaskHandlerAbsolutePath. Only those requests that are determined to be recurring tasks processed in a more thorough manner, à la:
internal RequestAnalysis AnalyzeRequest(HttpRequest request)
string absolutePath = request.Url.AbsolutePath;
if (absolutePath.StartsWith(_RecurringTaskHandlerAbsolutePath, StringComparison.Ordinal))
var analysis = new RequestAnalysis();
analysis.IsRecurringTask = true;
int parameterStartingIndex = _RecurringTaskHandlerAbsolutePath.Length;
if (absolutePath.Length > parameterStartingIndex)
int taskParameterDelimiterIndex = absolutePath.IndexOf('/', parameterStartingIndex);
if (taskParameterDelimiterIndex < 0)
if ((absolutePath.Length - parameterStartingIndex) == 32)
if (Guid.TryParseExact(absolutePath.Substring(parameterStartingIndex), "N", out heartbeatId))
if ((absolutePath.Length - taskParameterDelimiterIndex) > 1)
if (long.TryParse(absolutePath.Substring(taskParameterDelimiterIndex + 1),
analysis.TaskIdentifier = absolutePath.Substring(parameterStartingIndex,
taskParameterDelimiterIndex - parameterStartingIndex);
Between the details in your web.config and the code in your
ReportController, you've managed to keep all of your business logic encapsulated within your web application. That makes it easier to maintain long-term. As far as that nightly, summary report, all you had to do was write the
DailySummary() method. Now that is simple and elegant. Nice!
- [2014.May.19] Initial post.
- [2014.May.19] Added 'Further Reading' section.
- [2014.May.23] Amended 'Further Reading' section with UrlValidator, a Project Widget used by Revalee.