ScriptManager.LoadScriptsBeforeUI Explained

A typical ASP.NET AJAX page will contain a fair amount of script references. Sometimes more than you realize, because components you use, such as server controls from the ASP.NET AJAX Control Toolkit, automatically include library scripts that enable their client-side magic. Sometimes you include references explicitly through the ScriptManager's ScriptReferences collection.

The LoadScriptsBeforeUI property is all about performance tweaking. Normally you should leave it at its default value, true. But when should you set it to false, and what sort of implications does it have?

Whether scripts are included on your page because of server-side components requesting them, or because you explicitly listed them in the Scripts collection, they are rendered as script elements in your page. For example, the following script reference:

<asp:ScriptManager ID="ScriptManager1" runat="server">
    <Scripts>
        <asp:ScriptReference Path="~/BigScript.ashx" />
    </Scripts>
</asp:ScriptManager>
 

Will render into your page's HTML as:

<script src="BigScript.ashx" type="text/javascript"></script>

The question is -- should this script reference be before, or after the rest of the page's HTML?

When the browser encounters a script tag with a "src" attribute, it will pause the rendering of the page, download the script, execute the contents, and then proceed with rendering the page. By default, script references are placed at the top of the page (the beginning of the form element, actually), which is usually before all the rest of the HTML of the page.

Normally scripts load very quickly and are cached (they are similar to having images on your page, which the browser loads separately) so this is not an issue. But if the scripts you are referencing are numerous, large, or for whatever reason take a long time to download, the UI of your page will be on hold for a noticeable amount of time. From the end user perspective, the page will take longer to load than if there were no script references.

If you set LoadScriptsBeforeUI to false, then the script tags will render at the bottom of the form, AFTER all the UI of the page. Since the browser is able to render all the HTML before downloading the script references, the page will display and become interactive much sooner. It will still take just as long for the scripts to download, but they download while the UI has already been rendered, so there is an increase in the "perceived performance" of the page. The time between when the user clicks on a link to when they see UI and can interact with it no longer includes the time it takes to download external scripts.

To illustrate, lets create a page to measure how long it takes for certain events to happen, and see how the setting affects things. First of all, we will create an HttpHandler to serve as a slow loading script. To simulate a slow loading script, we use Thread.Sleep. The script itself calls a function declared on the page so we will know when it has loaded.

<%@ WebHandler Language="C#" Class="BigScript" %>
using System;
using System.Threading;
using System.Web;
 
public class BigScript : IHttpHandler {
 
    public void ProcessRequest (HttpContext context) {
        // simulate a slow-loading script
        Thread.Sleep(3000);
 
        context.Response.ContentType = "text/javascript";
        context.Response.Write("var scriptVariable = true;\r\n");
        context.Response.Write("logTimer('Script Loaded');");
    }
 
    public bool IsReusable {
        get { return false; }
    }
}

If we just include this in BigScript.ashx, we can now reference it like it were an actual javascript file (you may also use this kind of technique to reference dynamically created javascript libraries). We'll also include some javascript on our page to log when certain events happen. We'll use an ASP Button to toggle the LoadScriptsBeforeUI setting so we can play with each scenario easily. And just for a more real-world feel, we'll include tons of content in the page -- in this case, we'll insert a UserControl five times, where that user control simply contains the contents of the ScriptManager summary documentation page from the ajax site. Then finally once the page has finally loaded, we alert a report showing us what happened.

Here is a complete listing of the page:

<%@ Page Language="C#" %>
<%@ Register Src="HugeContent.ascx" TagName="HugeContent" TagPrefix="uc1" %>
<script runat="server">
    public void cmdBeforeUI_Click(object sender, EventArgs args) {
        this.ScriptManager1.LoadScriptsBeforeUI = true;
    }
    public void cmdAfterUI_Click(object sender, EventArgs args) {
        this.ScriptManager1.LoadScriptsBeforeUI = false;
    }
 
    protected override void Render(HtmlTextWriter writer)
    {
        if(this.ScriptManager1.LoadScriptsBeforeUI) {
            this.Page.Title = "Scripts Loaded BEFORE UI";
        }
        else {
            this.Page.Title = "Scripts Loaded AFTER UI";
        }
 
        base.Render(writer);
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <script type="text/javascript">
    var startTime = new Date();
    var log = '';
    function logTimer(msg) {
        var time = (new Date() - startTime);
        log += time + ': ' + msg + '\r\n';
    }
 
    function pageLoad(sender, args) {
        logTimer("pageLoad");
        alert(log);
    }
    </script>
</head>
<body>
 
    <form id="form1" runat="server">
        <script type="text/javascript">
            logTimer("Start of Form");
            logTimer("scriptVariable defined? " + (typeof(scriptVariable) !== 'undefined'));
        </script>
 
        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <Scripts>
                <asp:ScriptReference Path="~/BigScript.ashx" />
            </Scripts>
        </asp:ScriptManager>
        <div>
            <asp:Button ID="cmdBeforeUI" runat="server" Text="Before UI" OnClick="cmdBeforeUI_Click" />
            <asp:Button ID="cmdAfterUI" runat="server" Text="After UI" OnClick="cmdAfterUI_Click" />
            <uc1:HugeContent ID="HugeContent1" runat="server" />
            <uc1:HugeContent ID="HugeContent2" runat="server" />
            <uc1:HugeContent ID="HugeContent3" runat="server" />
            <uc1:HugeContent ID="HugeContent4" runat="server" />
        </div>
 
        <script type="text/javascript">
            logTimer("End of Form");
        </script>
    </form>
</body>
</html>

The logTimer() function will make a note of how long it has been since the page first starts to render each time it is called. Our dynamic ASHX script will call the function when it loads. We also call it at the beginning of the form, and at the end of the form after all the content. We call it from pageLoad(), the event ASP.NET AJAX fires when the ajax library has initialized all the components on the page, and then report the accumulated log entries.

When you run it, you get this:

Loading scripts before UI...

These numbers are in milliseconds. The dynamic script we are loading sleeps for 3 seconds before responding. So you can see here that the script loaded (and executed) after about 3.5 seconds. Then in just 2 tenths of a second later, the page was completely finished loading. So obviously, we the user spent most of those 3 seconds staring at a white page while the page loaded. The form didn't even START loading into view until 3.6 seconds after I navigated to the page.

Notice this "scriptVariable defined?" entry in the log. It says true. The script we load not only calls the logTimer function, it defines a variable. We attempt to read the value of that variable from inline script in our page:

<form id="form1" runat="server">
    <script type="text/javascript">
        logTimer("Start of Form");
        logTimer("scriptVariable defined? " + (typeof(scriptVariable) !== 'undefined'));
    </script>

Since the script is loaded "before UI", by the time our inline script executes, the script is loaded and all its libraries, variables, functions, or what have you will be available to us.

Now lets click the "AFTER UI" button...

Loading scripts after the UI...

There's a big difference here as you can see!

The beginning of the form loaded after just 89 milliseconds. The end of the form after only 122 milliseconds! That's about a tenth of a second. From an end users point of view, that is nearly instantaneous with only a very brief "flash". Because the browser renders UI as it encounters it, the page will not only display, but will be completely interactive by this point in time.

Then notice that our script loaded log still says about 3 seconds. It still takes 3 seconds to load, it's just delayed until later. The pageLoad ASP.NET AJAX event does not fire until all script references have loaded and all the components on the page are initialized, so it too waits for 3 seconds, waiting until that script finally downloads.

However... take another look at our "scriptVariable defined?" entry... this time, it's false.

It may seem obvious why that is now, but you need to be keenly aware of the consequences of this when you write real pages and decide to turn LoadScriptsBeforeUI off. The variable isn't defined because the script that defines it has not loaded yet when the form renders.

In a real world scenario this is a very important difference. The script may contain libraries that you may be depending on via inline script. If you want to delay loading it until after the UI, you will have to consequently delay your usage of that library until after the pageLoad event. No problem you might say, it's worth the perceived performance right?

Remember that the UI is interactive while the scripts are downloading. So let's say you have an AJAX Toolkit ConfirmButton on your page. The button will be usable by the user before this component has a chance to initialize -- so by delay loading the script, you are very much increasing the chance that the user clicks on it before the confirm behavior has been initialized, by passing your component entirely.

Another pitfall is if you hookup event handler declaratively. Perhaps you want to do some validation when a user clicks on a button, and you've referenced some validation library that makes it easy on you. A typical way to wire up the event is declaratively by calling a javascript function like so:

<script type="text/javascript">
    function DoValidation() {
        SomeExternalValidationLibrary.ValidateZipCode($get('zipCode'));
    }
</script>
<input type="button" onclick="DoValidation()" value="Validate" />

If "SomeExternalValidationLibrary" is a type that a script reference defines, and you have opted into loading that script "after" the UI, and the user clicks on the button before the script has actually finished loading -- guess what, you're referring to a type which does not exist, and the user is going to get a script error.

There are a couple of ways you can deal with that but they all have disadvantages. Generally it's best to keep things in the pageLoad event, so hookup the event there:

<script type="text/javascript">
    function pageLoad(sender, args) {
        $get('cmdValidate').onclick = DoValidation;
    }
    function DoValidation() {
        SomeExternalValidationLibrary.ValidateZipCode($get('zipCode'));
    }
</script>
<input id="cmdValidate" type="button" value="Validate" />

This will prevent the script error if the user happens to click on it before the scripts have loaded, but it simply won't do anything, which isn't the greatest experience either. If this were an actual submit button, not only would the validation not occur, but the form would be posted (note: you shouldn't be relying purely on client-side validation anyway).

Now, just to keep the record straight, in general you should avoid "inline" script when using AJAX components. Components are not initialized until pageLoad, so you shouldn't expect things like $find() to find anything, or for behaviors or client side controls to be available yet. Sometimes though you only need to refer to a type or function defined by an included script, and in those cases you can get away with inline script (unless you turn off LoadBeforeUI!), so I'm not saying you should "never" have it.

So there you have it. Basically if you definitely do not depend on inline script referencing included script, and you don't necessarily mind if the page is interactive before components (like the ConfirmButton) have been initialized, and you are concerned about the fastest possible performance (whether 'perceived' or actual), then LoadScriptsBeforeUI=false is something you should seriously consider taking advantage of. But it's an advanced feature, so be sure you know what the consequences are :)

Oh and by the way, while my example page did it programmatically, you can of course set the property declaratively.

<asp:ScriptManager ID="ScriptManager1" runat="server" LoadScriptsBeforeUI="false">
    <Scripts>
        <asp:ScriptReference Path="~/BigScript.ashx" />
    </Scripts>
</asp:ScriptManager>

Happy coding!

 

8 Comments

  • the question is still:
    why is the default value true?
    why is the setting for all scripts and not selectable for each?

  • I want to read this post, because it's interesting, but I won't because of the colors of the code samples. Perhaps you should consider changing the colors?

  • preishuber -- 'true' is a safer setting since it allows inline script to reference the scripts, which is what most devs would expect to be able to do (and is indeed what many did with the beta and ctp versions). It also reduces the time it takes for the UI to be initialized by script components, which is akin to a "flash of uninitialized content". Since typically there are only a few scripts, small to medium in size, and cached on the client, the pitfalls are not typically worth the benefit, so 'true' is the most logical setting.

    Being able to set it for each script can get complex and confusing. Scripts with "false" would have to be after scritps with "true", which suddenly re-orders them. Scripts often have dependencies on one another and so order is important. It's also ambiguous what should happen if a script component registers a script, and the page registers the same script, but the LoadBeforeUI setting conflicts between them. All these complexities would only make the feature confusing and make it difficult to use correctly, so its just a global setting for all scripts. If you really need only some scripts to be at the bottom, you can include them manually in your markup. If its a component script you can even suppress its usual reference using the script manager's resolving script reference event.

  • Mike -- perhaps you should read this:

    http://weblogs.asp.net/infinitiesloop/archive/2006/08/06/Join-the-Dark-Side-of-Visual-Studio.aspx

    I'm sorry the colors don't agree with you. I'd like to include a button that allows you to swap the color scheme of this blog, but I'm on a hosted community server and can't.

  • Great post, very informative. I happen to agree with Mike about the code samples. I agree whole heartedly with your Dark Side of Visual Studio and everything. I am using a dark theme for Visual Studio, too. But the colors I am using are a little softer on the eyes. In your code samples, the black mixed with the red pierces my eyeballs. I feel like my eyes are going to bleed and fall out my skull if I stare at the code for longer than a few seconds. Maybe if you just changed the red to something else...?

    I enjoy reading your blog besides the eye-bleeding code samples. :)

  • Chad -- very well, point taken. How's a little light blue (updated)?

  • Nice post! I hadn't seen this talked about before.

    IMHO, perceived performance gains go right out the window once I hit an error or some code that should have prevented me from doing something stupid isn't loaded yet. The perceived performance gain isn't even real; it's perceived.

    I honestly hope that nobody does this until they compress their JS file and make sure it's caching properly on the browser. If there's still a performance problem, I'll listen to their reasoning on why they set this property = true.

    Sorry, but I'm actually shocked that MSFT even included this property in the ScriptManger. It seems like it could cause so many difficult to track down bugs. Those bugs that appear at seemingly random times ... you know, our favorite ones!! I love the .NET style programming that ASP.NET AJAX brings to the table, but JavaScript isn't compiled with type checking, it's interpreted and it's important that we keep this in mind as we develop features and pages.

    Final thought: what if I have a really evil 3rd party control developer that gets a handle to the ScriptManager object sitting on the page and sets the property to false (is this possible?). Awesome! My page has a bug in it because of some code that I can't see or debug. I can hear the hours ticking away tracking this one down.

  • Joel -- all excellent comments. The fact it is easy to shoot yourself in the foot with the setting set to false is exactly why the default is true, despite the fact that in the RC version, scripts essentially loaded after the UI by default.

    Perceived performance is important -- many ajax apps for example that do partial updates may not actually do what they do "faster", but it "feels" faster and that has a big impact on usability. And the fact is the page really does load/display/become usable quicker, so in that respect it is "real" performance.

    You also have to realize that the reason it exists is essentially because of customer feedback. It may not ever make sense in your scenarios, but there are sites out there that can potentially have dozens upon dozens of scripts for a single page. There are of course other ways of dealing with that, like combining them all into one script, but that isn't always a feasible option either. This setting gives sites like that an added option they wouldn't have had otherwise, which could have been an adoption blocker.

    About your final thought -- sure, component code can indeed flip the switch. That would be a poor component of course. But it isn't new that a component could do something it shouldn't and negatively impact your page. Many 3rd party controls make very, very poor use of ViewState for example. A component could alter the control collection outside its realm if it wanted. A component could disable viewstate, change your page's title, register virtual path providers, add http headers -- all things that could alter the behavior of your page. Component developers have always had to be responsible for fitting well with the pages they don't know they will exist within, this setting, and everything else on the script manager, is no different.

    If you really want to be sure the setting doesn't flip on you, you could add a debug assertion -- then at least your code will tell you when someone is being bad. Not a bad idea when you know you have inline code that would break otherwise.

Comments have been disabled for this content.