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:
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:
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...
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!