Tuesday, September 13, 2005 - Posts

Reducing counter for web applications revisited: Using WebResources for embedded Javascript in ASP.NET 2.0

That was a long title. I apologise to anyone who was exhausted by reading that title and is now too tired to read the rest of this post, but such is life.

Back in March I gave an example of an ASP.NET server control as a solution to the problem of displaying an on-screen indication of the amount of time left to complete a task, something that is a common enough requirement in applications such as booking systems which give users the opportunity to purchase a product where availability is limited and often date/time-specific (I won't go into the connection between limited availability and limiting time to complete a transaction here: essentially it's a solution to the problem of users who may abandon the transaction half-way through, or be abducted by aliens or whatever).
      Many (perhaps most) server controls include client-side code, and this is likely to become more true with the current popularity of remote scripting (or AJAX to its new-found friends). While there is a recommended location for control-related script files in ASP.NET, this does not create a pretty deployment situation, and it could be argued that the dependency imposed by an external script file introduces an inherent fragility to the control. A common alternative (and the one I used in the code I illustrated in my March posting) is to construct the script as a string server-side in the OnPreRender event handler of the control.
The problem with that approach of course is that the script code (typically mingled with server-side variables and so on) is not only hideous but largely unreadable.
Any script block of more than trivial (really, really trivial...as in something Very Trivial Indeed) size created this way is effectively unmaintainaible. I know this because I have to maintain quite a lot of this sort of thing (in fairness, I wrote most of them).

ASP.NET 2.0 to the rescue...
Developers who have experience with Windows Forms (either the desktop or Compact Framework version) are likely to be familiar with the use of embedded resources to include a file (such as an image, a wave file, or pretty much anything) within an assembly so its content can be read at run time. This neatly deals with the external dependency problem, since the file is as I said above included in your assembly. The problem of course is that this approach doesn't work with web applications, which require a URL that can be resolved by the browser at run time.
The WebResource support in ASP.NET 2.0 neatly gets around this problem by providing a handler which is passed parameters that allow it to serve up the requested resource (see Handling Client Files in ASP.NET Whidbey by Victor Garcia Aprea in MSDN magazine, January 2004: while the article is of necessity based on the PDC 2003 build of Whidbey, it goes into the subject in great detail). The end result is that a script file, an image or a .css file (and presumably other types such as XSLT, although I haven't tried that) can now be compiled into your assembly so that the control can be distributed as a single unit (or as part of a control library which itself is a single unit - which is more likely). In the specific case of script files, this has the highly desirable result that you can now put your script in a civilised, readable script file.
In this particular case (and in a lot of others) it's necessary to make some changes to the actual JavaScript, but I'll deal with those later.
There are essentially two changes to make to the server-side source, as follows:

  1. In your AssemblyInfo.cs file (I'll assume C# for the purposes of illustration), add the following attribute declaration:
    [assembly: WebResource("DalySoftware.WebCounter.js", "application/x-javascript")]
  2. In the CountDown.cs file, change the OnPreRender event handler so that rather than building and registering the JavaScript as a string, you call Page.ClientScript.RegisterClientScriptResource to register the WebResource as client script (there are also some changes to the startup script call to accommodate the changes required to the JavaScript) . In this case the method now contains the following code:

    string key = "CountDownStart";
    string startScript = string.Format("decrementTimer(\"{0}\",\"{1}\",{2},{3});",this
    .ClientID,redirectPage,timeSpan.Minutes,timeSpan.Seconds);
    Page.ClientScript.RegisterClientScriptResource(typeof
    (CountDown), "DalySoftware.WebCounter.js");
    Page.ClientScript.RegisterStartupScript(typeof(CountDown), key,startScript, true
    );
    base.OnPreRender(e);

The JavaScript method decrementTimer should now be placed in a JScript file and given the  Build Action "Embedded Resource".
Because our JavaScript is now in a self-contained file rather than being mixed with server code, we pass it additional parameters to identify the names of the control and the page to which the user is redirected if the timer expires, e.g.

decrementTimer(id,redirectPage, minutes, seconds)

 - This is the reason for the extra parameters passed to decrementTimer in the startScript definition.
The same change is reflected within the function because of its recursive nature (I know that's not particularly clear, but if you read the script you'll see what I mean)