Friday 21 May 2010

Recursive Timers Not Firing

Wasted a good while today trying to find out why some Timers I had set up kept stopping firing. Here's the scenario: I have a windows service that does 2 things.
1) Generate Alerts: Periodically generate a batch of alert emails to send to all users of a system reminding them of pending tasks they have assigned to them.
2) Send Emails: For all emails in a database table, send them.

Generate Alerts parses a cron format string to determine how often to run. Send Emails has an interval in milliseconds. One important point here is that I don't want these tasks to run every X seconds. I want them to run after X seconds and then again X seconds after the previous run has completed. Hence I don't want to use a Timer in the standard way as one execution could begin while another is still in progress.

For this reason, in each case I create a timer to do my work, and then at the end of the timer I create another timer. Note these are the Timers from System.Threading and are configured to run only once, not at every tick). Here's an example:


public void StartEmailSender(int waitTime, int retryAttempts)
{
log.Debug("Starting EmailSender");
WaitTime = new TimeSpan(0, 0, 0, 0, waitTime);
RetryAttempts = retryAttempts;

//Set the SendQueuedEmails method to run after the elapsed time.
emailSenderTimer = new Timer(delegate(object s)
{
SendQueuedEmails();
}
, null, WaitTime, new TimeSpan(-1));
log.Debug("EmailSender Started");
}

public void SendQueuedEmails()
{
log.Debug("Entering SendQueuedEmails method");

//Code to send emails goes here

//Create a new timer to run this method again after the elapsed time.
emailSenderTimer = new Timer(delegate(object s)
{
SendQueuedEmails();
}
, null, WaitTime, new TimeSpan(-1));

log.Debug("Exiting SendQueuedEmails method");
}


The original problem I was having is that the timer would run X number of times and then stop. Or one of the 2 timers would continue for longer than the other but then eventually stop. I discovered the reason was that my Timers were being Garbage Collected.

The above code is the working version. In my previous version which did not work I was not assigning the output of "new Timer(delegate(object s)" to anything. Now it gets set to a private class member. This ensures that there is always a reference to the current Timer so long as the class has not been disposed. I ensure the class is not disposed by again having a private class member in the service's main class which I initialise in the OnStart method. Hence the class is not disposed until the service is stopped and hence there is always a reference to the current Timer which is waiting to execute, and only the ones which have already completed get disposed.

Wednesday 5 May 2010

Using Refresh Files with WebSite Projects to include DLLs

Recently I had a problem whereby my WebSite project (Not WebApplication project) in Visual Studio was missing some DLLs. These are pre-built DLLs that I load from a set location, for example AjaxControlToolkit.dll and log4net.dll.

On my build server whenever a build was done I needed a way to have these files copied to the BIN directory of my website. In a WebApplication or other project these references are specified in the project file (Project.csproj for example), but we don't have a project file for a WebSite project. The WebSite is just a directory of files and any "project" references are included in the Solution file.

The way Visual Studio allows us to include DLL files which are not covered by the project references is with the use of a Refresh file. So for example if I want to include C:\DLLs\AjaxControlToolkit.dll in my WebSite I add it with Add Reference -> Browser -> Choose the file. Visual Studio then puts a file called AjaxControlToolkit.dll.refresh in my bin directory. The presence of this file cause the AjaxControlToolkit.dll to be copied from C:\DLLs\ to the BIN directory of my WebSite each time I build it.

Normally we don't check things in the BIN directory into source control, but Refresh files are the exception. I added my Refresh file in the BIN directory to CVS and when my Build Server did a check out it got the Refresh file, causing the build to copy the DLL into the BIN directory for my built website.

Monday 3 May 2010

Query String Encoding Hassles with & in ASP.Net

Today I encountered an odd problem which I remember having years ago but had no memory of how to solve it....

So I'm creating a URL with a query string which will appear in a web page:
thumbnailImage.ImageUrl =  "~/UserControls/ImageHandler.ashx?" + "id=" + productImages[0].UId + "&thumb=true&size=100"
Now when this appears in the web page, it seems that all of the "&" symbols, which represent the delimiter between parameters, get changed to "&"

The problem with this is that when the page that is linked to resolves the query parameters I get:

context.Request.Params["thumb"]
null

context.Request.Params["amp;thumb"]
"true"
The first thing I tried was to use Server.URLEncode. This changes the "&" symbols to "%26". This in itself is no use, but when used with Server.URLDecode means we end up with the correct query string. It still gives us a problem however because we can no longer use the string index for context.Request.Params. We'd have to manually parse the string output of URLDecode to find the required parameter. Not difficult I know, but a little untidy.

The solution I eventually found was to use:
thumbnailImage.ImageUrl = String.Format("~/UserControls/ImageHandler.ashx?" + "id=" + productImages[0].UId + "&thumb=true&size=100");
It appears that the String.Format causes the "&" symbols not to be changed to "&"

Exactly why that happens I do not know, it seems that the output of String.Format is the same as the original string:

String.Format("~/UserControls/ImageHandler.ashx?" + "id=" + productImages[0].UId + "&thumb=true&size=100").Equals("~/UserControls/ImageHandler.ashx?" + "id=" + productImages[0].UId + "&thumb=true&size=100")
true
Strange... but it seems to work.