Image taken from http://picardfacepalm.com/
Recently at work I was tasked with something that sounded simple enough build a web app that would send out email one a schedule.
Then I realized that I can’t use a web service, scheduled job or basically anything other than a simple web app. The reason for this is that I don’t even deploy my own apps at my current job. I compile my code and send it off to have it deployed.
I set out to accomplish this using only a web app.
First web apps are normally something that responds to an events, since I can’t use a scheduled task I’ll had to find a way for the application to trigger itself.
After some hunting on the web I found several articles/code samples some used a thread others used the HttpRuntime class and the application cache. It looked surprisingly simple and so I had to try it and see if it worked.
Since I want my schedule task to always be running i decided the best place to start it up would be in Global.asax.vb as such I added it to my project and started coding. The end result was the following:
Public Class Global_asax
Dim onRemove As CacheItemRemovedCallback
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
Sub AddTask(ByVal name As String, ByVal seconds As Integer)
onRemove = New CacheItemRemovedCallback(AddressOf Me.RemovedCallback)
Public Sub RemovedCallback(ByVal k As String, _
ByVal v As Object, _
ByVal r As CacheItemRemovedReason)
If k = "BackgroundTask" Then
'This is where you can run your code
On Line 7 I’m creating a class wide CacheItemRemovedCallback variable called onRemove this variable will be used later to tell the application what sub procedure to call when an item is removed from the cache.
In the Application_Start (Lines 9-11) I’m calling AddTask, as the application is only started once this eliminates the need for me to check and ensure that my task hasn’t already been added, and important step if your using this idea somewhere else then in Application_Start.
Next is the AddTask sub procedure (Lines 13-22) I’ve formatted it for this post but it’s essentially two lines, the first creates a new CachedItemRemovedCallback and passes in the address of the RemovedCallback sub procedure. The second inserts a new item into the HttpRuntime Cache the full details can be found on MSDN the important part in this code is that we set the key to the name of our task (Line 15), the value to the frequency we want it to repeat (Line 16), it’s expiry to the current time plus the frequency (Line 18) and our CacheItemRemovedCallback variable (Line 21).
What this will do is add an item into the cache set to expire in 60 seconds, once it expires it will call the RemovedCallback (Line 24-31) procedure and pass into it the key, value and reason. This is where we can now check that the item removed is the same as we added (Line 27) and perform our task (Line 28) lastly to ensure this task runs again in 60 seconds we call AddTask and pass in the same values (Line 29).
And voila you application will now run this “task” every 60 seconds.
Now we only have two little problems to deal with what happens if you application does ideal and IIS end’s it and what happens with IIS recycles the application pool… The solution, after many experiments was to create a second web app. Considering what function this web app would do I decided to call it the FrontDesk since my main app would call it and request a wakeup call.
to call it I added the following code into another sub procedure and called it from the Application_End sub procedure.
Public Function PingServer(ByVal URL as String) As String
Dim request As WebRequest = WebRequest.Create(URL)
request.UseDefaultCredentials = True
request.PreAuthenticate = True
request.Credentials = CredentialCache.DefaultCredentials
Dim response As WebResponse = request.GetResponse()
Dim dataStream As Stream = response.GetResponseStream()
Dim reader As New StreamReader(dataStream)
Dim responseFromServer As String = reader.ReadToEnd()
Catch ex As Exception
Return "Error:" & ex.ToString
Essentially this PingServer sub procedures fetches the website of any URL you pass into it, this has the added benefit of waking up any .net web app. The FrontDesk web app simple takes in a URL via the QuerryString sets up a task 30 seconds in the future, in order to allow the application to end gracefully, then using the same PingServer method wakes up the main application.
This is by no means how I would ever suggest you should do scheduled tasks unless you have no alternative, also something to note this application will be run within a private network as such I couldn’t use any solution that would be coming from outside the network.