Registering scripts in ASP.NET controls (the right way)

Let's start out with an innocent control that registers a simple script file include:

    public class BaseControl : Control {
        protected override void OnPreRender(EventArgs e) {
            Page.ClientScript.RegisterClientScriptInclude(
                GetType(),
                "InitScript",
                ResolveClientUrl("~/ScriptLibrary/BaseControlInitScript.js"));
            base.OnPreRender(e);
        }
    }

All the script file does is show a little alert message when it is loaded. Stick as many of these BaseControl controls as you want on a page and the script gets included only once. This happens because ASP.NET uses the first parameter (type) and second parameter (key) in the call to RegisterClientScriptInclude to determine the uniqueness of the script. Now a customer decides to write a custom control that derives from my control and for whatever reason decides to add no new functionality (not that it matters, but this customer is really lazy!):

    public class DerivedControl : BaseControl {
    }

What happens if you add one BaseControl and one DerivedControl to the page? How many scripts get included? Well, let's just say that I wouldn't be writing this blog if the answer was only one.

Since the BaseControl used GetType() as its type parameter for RegisterClientScriptInclude, the return value of GetType() is unique for each type that derives from BaseControl. As we all know, typeof(BaseControl) != typeof(DerivedControl). Since the types are unique (and thus distinct), ASP.NET concludes that these are two distinct requests to register a script and it gets included twice in the rendered page.

The fix, fortunately, is very simple. Just replace GetType() with something constant, such as typeof(BaseControl). This way when the code executes in the context of DerivedControl, the constant Type value remains the same, and ASP.NET removes the duplicate registration request.

At this point you might wonder why ASP.NET detects duplicates based on type and key instead of based on URL. The answer: I have no idea why. A possible reason that it was done is that the same script URL might return different results each time. For example, maybe it's a script for advertisements and the URL is http://www.example.org/ads/AutomaticAd.aspx and it returns different script for each request. If ASP.NET eliminated duplicates it might cause a site to not work properly. Another reason is that different URLs get canonicalized to the same value. Should ASP.NET consider "foo.js" and "FOO.js" the same? On Windows they're the same, but on Unix they are not. Keep it simple.

The reality is that it doesn't matter much anymore why it was done since that's the way it is. The important part is that you have to be careful when you're writing controls that register scripts. Go take a look at all your calls to ASP.NET's Page.ClientScript and ASP.NET AJAX's ScriptManager and see what types you're passing in.

 

Unfunny story: In my numerous years reviewing other people's sample ASP.NET controls you wouldn't believe how many people decided it was a good idea to pass in typeof(int) for the type parameter and the string "key" for the key parameter. Now I wonder how many people used two different sample controls on the same page and realized that they don't work together.

3 Comments

  • What exactly is the difference between Control.ResolveUrl() and Control.ResolveClientUrl() and any recommendations on why one should be used over the other?

  • Great post. Thanks.

  • Hi,

    I was reading your article and I would like to appreciate you for making it very simple and understandable. This article gives me a basic idea of Registering Client script in asp.net and it will help me a lot. Check this link...
    http://www.mindstick.com/Blog/220/Registering%20Client%20script%20in%20asp%20net

    It is also helpful for a beginner.

    Thank you very much!

Comments have been disabled for this content.