Wrox Home  
Search
Professional ASP.NET 2.0 AJAX
by Matt Gibbs, Dan Wahlin
June 2007, Paperback


Excerpt from Professional ASP.NET 2.0 AJAX

Using the ASP.NET AJAX ScriptManager

by Matt Gibbs

AJAX development centers on using more JavaScript. With increased use of JavaScript comes the need for better ways to manage, reference, localize (that is, provide different script versions for specific language and culture combinations), and transmit script code to the client browser. The ASP.NET ScriptManager is at the center of ASP.NET AJAX functionality. The ScriptManager is the key component that coordinates the use of JavaScript for the Microsoft AJAX Library. Custom controls also use it to take advantage of script compression and reliable loading, as well as for automatic access to localized versions of scripts.

The Ubiquitous ScriptManager

A ScriptManager is required on every page that wants to use the AJAX Library. When the ScriptManager is included in the page, the AJAX Library scripts are rendered to the browser. This enables support for partial page rendering and use of the ASP.NET AJAX Client Library. Listing 1 (Bare.aspx) is a page with a barebones ScriptManager that does nothing more than render the Microsoft AJAX Library files to the browser

Listing 1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html 
xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>ASP.NET AJAX ScriptManager</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server" />
    <div>
    </div>
    </form>
</body>
</html>

Notice how the rendering changes when the ScriptManager is added to the page. You get five new script elements in the HTML sent to the browser.

The following HTML is part of what is sent to the browser when Bare.aspx is requested. You can see that querystring parameters in the script references are long. They include time stamp elements and unique hash identifiers for script that is registered dynamically. If you copy the path from the src attribute of the script element and paste it into your browser's address bar, you will get the actual script resources returned from the server.

<script type="text/javascript">
<!--
var theForm = document.forms['form1'];
if (!theForm) {
    theForm = document.form1;
}
function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
        theForm.__EVENTTARGET.value = eventTarget;
        theForm.__EVENTARGUMENT.value = eventArgument;
        theForm.submit();
    }
}
// -->
</script> 
<script src="/chapter05/WebResource.axd?d=6kIHBZsykATBSq3fbEmsYQ2&t=
632968784944906146" type="text/javascript"></script>

<script src="/chapter05/ScriptResource.axd?d=9x_HPpGK-eN7tqV0Ff_J6PdW6
RyjdfhhTabpkRyiakKJ_a_q_nueOi1SMgVHCnyemAE_Wi1zmQc6dppn1_ShJ3RT853s6OS8dnx
NpQibyXs1&t=633054056223442000" type="text/javascript"></script>
<script src="/chapter05/ScriptResource.axd?d=9x_HPpGK-eN7tqV0Ff_J6PdW6
RyjdfhhTabpkRyiakKJ_a_q_nueOi1SMgVHCnyemAE_Wi1zmQc6dppn1_ShJ_clCUeTRolYBVlv
TyhBCrs1&t=633054056223442000" type="text/javascript"></script>

The first script is rendered inline in the HTML without a callback to the server. There is no src attribute on the script element. It is the method for initiating a postback. The second is a script reference for the approximately 500 lines of JavaScript included in most ASP.NET pages. It is the fundamental script providing some of the essential functionality of ASP.NET 2.0, including callbacks, performing validation, and managing focus.

The next script reference is for the MicrosoftAjax Library. It is the same as the MicrosoftAjax.js file copied to the \Program Files\Microsoft ASP.NET\ASP.NET 2.0 AJAX Extensions\v1.0.61025\MicrosoftAjaxLibrary\System.Web.Extensions\1.0.61025 directory when installing ASP.NET AJAX. The code can be used with other server technologies, as it is focused on enriched client-side development. There are debug and release versions of the script files. The resources are also embedded in the System.Web.Extensions.dll and then extracted by the HTTP request to the ScriptResource.axd handler. This is the JavaScript that supports the client-side type system and Base Class Libraries as discussed Chapter 4, "Understanding the ASP.NET AJAX Client Library," of the book, Professional ASP.NET 2.0 AJAX (Wrox, 2007, ISBN: 978-0-470-10962-5).

The third script reference is for the MicrosoftWebForms Library. This code is retrieved as a resource from the dll by default, but is also copied onto disk. These scripts provide support for the UpdatePanel and the lifecycle of events associated with using partial page rendering.

The other two script entries are rendered inline in the page. You can see this in the following code, which is again part of the page output from requesting Bare.aspx from Listing 1. The first is for the PageRequestManager that handles partial page updates. The second piece of script performs the primary startup of the client page lifecycle by initializing the application object.

<script type="text/javascript">
//<![CDATA[
Sys.WebForms.PageRequestManager._initialize('ctl02', document.getElementById('form1'));
Sys.WebForms.PageRequestManager.getInstance()._updateControls([], [], [], 90);
//]]>
</script>
<script type="text/javascript">
<!--
Sys.Application.initialize();
// -->
</script>

You have seen that including the ScriptManager in the page results in rendering the scripts necessary to use the client-side Microsoft AJAX Library and to take advantage of partial page rendering as well. It also renders the scripts necessary for initiating the lifecycle of JavaScript events.

Adding Script References

The ScriptManager element can contain a scripts collection for adding scripts to the page. The way you would typically include a separate file of JavaScript in a page would be to use the HTML script element. You set the type attribute to "text/javascript" and the src attribute to the path of the JavaScript file, as shown in the following code.

<script type="text/javascript" src="script.js"></script> 

Instead of writing the script element directly, the script can be added to the set of scripts that the ScriptManager controls using a ScriptReference element. By including it this way, I am assured that it will be loaded at a point when the ASP.NET AJAX Library is available. This is shown in Listing 2 (ScriptReference.aspx).

Listing 2
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" 
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
 <title>ScriptManager</title> 
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="ScriptManager1">
    <Scripts>
        <asp:ScriptReference Path="sample.js" />
    </Scripts>
    </asp:ScriptManager>
<div>
</div>
</form>
</body>
</html>

You would expect that requesting this page would result in the same output as running Bare.aspx from Listing 1 (with one additional script element), but this is not exactly the case. After the initial request for the page, everything appears to be the same, but during partial page updates when asynchronous updates are happening and scripts are being loaded, a JavaScript error is encountered, as shown in Figure 1.

Professional ASP.NET 2.0 AJAX : Figure 1
Figure 1

A call to Sys.Application.notifyScriptLoaded is required so that the ScriptManager can provide the client lifecycle reliably on various browsers. Not all browsers provide an event in their DOM for detecting this automatically. The script would actually run fine without it, but to ensure cross-browser support, the ScriptManager requires the call. Listing 3 (Sample.js) shows the recommended way of modifying scripts so that they can provide the required call to the Microsoft AJAX Library when it is present, and still function reliably when used separately.

Listing 3
// filesystem based script resource
function identityFunction(arg) {
    return arg;
}
if(typeof('Sys') !== 'undefined') Sys.Application.notifyScriptLoaded();

Rather than directly making the call, I first need to check that the Sys namespace is defined. The script can then be included in a page where the ASP.NET AJAX Library is not being used without causing an error.

Setting the ScriptMode

The ScriptManager and ScriptReferences both allow you to set the ScriptMode. The default value of this enum is Auto. The other possible values are Release, Debug, and Inherit. The ScriptMode determines whether release or debug versions of the scripts are used. When set to Auto, the determination is primarily the result of server settings. Debug scripts are used when debug is set to true in the compilation section of the web.config file, or when the debug page directive is set to true. The deployment setting in the configuration file trumps all of the other settings. When retail is set to true, all other debug settings are treated as false and you won't get debug versions of the scripts.

ASP.NET AJAX includes release and debug versions of the scripts, but for ScriptReferences that refer to files on disk, the ScriptManager does not assume that you have provided debug versions of the script, so the path given is used regardless of the server's debug setting. The Auto setting for ScriptMode is almost identical to the Inherits value, except for this behavior. Inherits will take the value directly from the server configuration. The ScriptManager doesn't look to configuration or page settings when the ScriptMode is set directly to Release or Debug.

The pattern you use to provide debug versions of scripts is to add .debug to the filename before the .js suffix. The name the ScriptManager would assume for the debug version of Sample.js would be Sample.debug.js. The filenames are not validated, so specifying the debug version of a script file that doesn't exist will result in an error like the one shown in Figure 2. Again, this error is only produced when script loading is validated during asynchronous postbacks, not during the initial loading of the page.

Professional ASP.NET 2.0 AJAX : Figure 2
Figure 2

The choice of using .debug in the script name is arbitrary, but it does allow automation in finding the right version of scripts on disk, as well as when they are embedded as resources in a dll. You won't have to keep swapping files during development and deployment. You can set the debug mode you want and get the right version for the scripts you want to debug. For file-based script resources, you toggle between the release and debug versions by setting the ScriptMode attribute to Debug directly on the ScriptReference.

Embedding Script Resources

There are some advantages to embedding JavaScript files as resources in a dll and deploying them that way. You don't have to wonder whether someone has modified the scripts on the server. You can maintain a set of release and debug scripts in a single location and use the ScriptManager to dynamically switch between versions. And once deployed, you have version information on the scripts, allowing for easier maintenance and servicing.

One key difference exists in how the ScriptManager treats scripts retrieved from the filesystem compared with those embedded as a resource in a dll. When the path to a script is used, the ScriptManager provides a callback to the ScriptResource handler, which retrieves the contents. The script itself is not modified (no extra calls are injected). When a script is retrieved as an embedded resource, however, the ScriptManager injects the call to Sys.Application.notifyScriptLoaded for you automatically. This allows you to start using scripts that you already have with ASP.NET AJAX without having to rebuild the dlls.

In Visual Studio, you first create a class library project. You can do this from your existing web application by right-clicking the Solution name in the Solution Explorer window. From the Add New Project dialog, you can add a new project by selecting the Class Library template (see Figure 3). Alternatively, you can choose to create a new Project from the File menu. In the dialog where you select the Class Library type you can also choose to add this project to the current Visual Studio Solution.

Professional ASP.NET 2.0 AJAX : Figure 3
Figure 3

You create the script files as you normally would, by adding JScript files to the project. Remember that using the naming convention of adding .debug into the filename for the debug version of a script alenables automatic switching between release and debug versions at runtime. To embed the scripts into the resulting dll, you set the Build Action in the Properties pane for the script file to Embedded Resource, as shown in Figure 4.

Professional ASP.NET 2.0 AJAX : Figure 4
Figure 4

In this example, I have added embeddedSample.js with a function called doubleArg that just returns double what it is passed. Listing 4 (embeddedSample.debug.js) includes some error checking. For some functions, you want to do parameter validation in release scripts, but for many situations, you just want the extra checks during development and testing.

Listing 4
function doubleArg(arg) {
    if(typeof(arg) === 'undefined') {
        throw Error.argumentUndefined('arg');
    }
    if(arg === null) {
        throw Error.argumentNull('arg');
    }
    if((arg % 0) === 0) {
        throw Error.argumentOutOfRange('arg', arg);
    }
 
    return 2*arg;
}

To be able to add a ScriptReference for the embeddedSample script, you need to define the WebResource for the project. The WebResource attribute is in the System.Web.UI namespace. The attribute is used in the code of your project to include the script into the compiled dll. The project needs a compile-time reference to the System.Web assembly to compile. Right-click the References entry of the Solution Explorer in Visual Studio and select Add Reference. Figure 5 shows the dialog where you can select System.Web.

Professional ASP.NET 2.0 AJAX : Figure 5
Figure 5

The WebResource is added to the AssemblyInfo.cs file, which is in the Properties directory of the class library project. The WebResource attribute tells ASP.NET the names and types of resources available from assemblies in your web project. In this case, there are two resources, and both of them are JavaScript files to be embedded in the assembly.

[assembly: WebResource("MyScripts.embeddedSample.js", "text/javascript")]
[assembly: WebResource("MyScripts.embeddedSample.debug.js", "text/javascript")]

If you create the class library as a project within your web project in Visual Studio, you can modify the properties of the project to have the resource assembly copied into the bin directory of your web application. If it is a separate project, you will need to explicitly set the output location or copy the dll into your application manually.

To declare the dependency on the separate project, right-click on the Web Project in the Solution Explorer and select the Add Reference option. From there, you can select the project with the embedded resources. Also, you have to select the script file in the Solution Explorer of Visual Studio, and in the Property Grid set the Build Action to the Embedded Resource value. This is the same place where you can choose to copy the output to a specific location when the dll is compiled.

Now you have a dll with script resources embedded, and can include them in your page with a ScriptReference. Listing 5 (EmbeddedReference.aspx) is a page that includes the script and calls to the doubleArg function with valid and invalid parameters.

Listing 5
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" 
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
 <title>ScriptManager</title>
 
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="ScriptManager1" >
    <Scripts>
        <asp:ScriptReference Name="MyScripts.embeddedSample.js" 
Assembly="MyScripts" />
    </Scripts>
    </asp:ScriptManager>
<div>
</div>
</form>
</body>
</html>
<script type="text/javascript">
function pageLoad() {
    alert(doubleArg(3));
    alert(doubleArg());
}
</script>

When debugging is not enabled for the page, the browser displays the number 6 and NaN. The second call to the function does not include an argument, and the release script just tries to double it and returns the "Not A Number" JavaScript value. When using the debug version of the script, however, you get the benefit of running the debug version of the script, and a better error message is displayed, explaining that the argument cannot be undefined.

You saw when using the path attribute of a ScriptReference to load a script from a file that the ScriptManager does not switch automatically between debug and release versions of the script. And when using the name and assembly attributes to specify loading of an embedded script, it will pick up the right version based on the server setting. If you do not provide a debug version of the script, the ScriptManager will notice this and fall back to the release version. It is not an error to skip inclusion of debug script resources.

You can include all three attributes — name, assembly, and path — in a single ScriptReference element and get a slightly different behavior still. The path will be used to retrieve the script from disk, but the determination of whether or not a debug version of the script is available is made by looking at the embedded resources. If you have a debug embedded resource and choose to get the script from disk, it is an error to try and retrieve the debug version if it does not exist on disk. The ScriptManager will not fallback from the filesystem to the embedded resource.

Script Localization and Globalization

The .NET Framework has good support for providing and using localized resources in .NET applications. This is a feature that has been lacking in JavaScript. ASP.NET AJAX makes it possible to provide localized string resources and have the correct language used automatically at runtime. Script localization allows you to provide for specific translations of text for use in JavaScript in the browser. Script globalization is the ability to format and parse data using a specific culture. For example, it is customary in U.S. English for dates to be formatted as month, followed by the day and then the year (8/21/1993). But in many other cultures, the day of the month is presented first (21/8/1993). Both script localization and script globalization are discussed with examples in Chapter 5, "Using the ScriptManager," of the book, Professional ASP.NET 2.0 AJAX (Wrox, 2007, ISBN: 978-0-470-10962-5).

This article is excerpted from Chapter, "Using the ScriptManager," of the book, Professional ASP.NET 2.0 AJAX (Wrox, 2007, ISBN: 978-0-470-10962-5) by Matt Gibbs and Dan Wahlin. Matt is currently the development manager of Microsoft's UI Framework and Services Team. This talented group is responsible for ASP.NET and the AJAX Framework as well as the new Integrated Pipeline of IIS 7. Matt has been working on Microsoft web technologies since joining the IIS 4 team to work on "classic"ASP in 1997. He has co-authored several books on ASP and ASP.NET.