The ASP.NET AJAX Infrastructure : The Script Manager Control

The main control in the server infrastructure of ASP.NET AJAX is the ScriptManager control and its twin, the ScriptManagerProxy control. You will find just one instance of the ScriptManager control in each ASP.NET AJAX page. No AJAX capabilities can be enabled in ASP.NET pages that lack a reference to one ScriptManager control. The ScriptManagerProxy control is used only in master pages scenarios to reference the original script manager from content pages.

The ScriptManager control manages and delivers script resources, thus enabling client scripts to make use of the JavaScript type system extensions and other JavaScript features that we covered earlier in this chapter. The ScriptManager control also enables features such as partial-page rendering and Web service and page method calls. The following code shows the simplest and most common way to insert the script manager in an ASP.NET page:

<asp:ScriptManager runat="server" ID="ScriptManager1" />

The control produces no user interface, works exclusively on the server, and doesn’t add any extra bytes to the page download.

Properties of the ScriptManager Control

The ScriptManager control features a number of properties for you to configure its expected behavior. Table 1 details the supported properties.

Table 1. Properties of ScriptManager
Property Description
AllowCustomErrorsRedirect Indicates whether custom error redirects will occur during an asynchronous postback. The property is set to true by default.
AsyncPostBackErrorMessage Gets and sets the error message to be sent to the client when an unhandled exception occurs on the server during an asynchronous postback. If this property is not set, the native exception’s message will be used.
AsyncPostBackSourceElementID Gets the ID of the server control that triggered the asynchronous postback. If there’s no ongoing asynchronous postback, the property is set to the empty string.
AsyncPostBackTimeout Gets and sets the timeout period in seconds for asynchronous postbacks. A value of zero indicates no timeout. The property is set to 90 by default.
AuthenticationService Gets an object through which you can set preferences for the client-side authentication service.
EnablePageMethods Indicates whether static page methods on an ASP.NET page can be called from client script. The property is set to false by default.
EnablePartialRendering Indicates whether partial rendering is enabled for the page. The property is set to true by default.
EnableScriptGlobalization Indicates whether the ScriptManager control renders script in the client that supports parsing and formatting of culture-specific information. The property is set to false by default.
EnableScriptLocalization Indicates whether the ScriptManager control retrieves script files for the current culture, if they exist. The property is set to false by default.
IsDebuggingEnabled Indicates whether the debug versions of client script libraries will be rendered. The debug attribute on the @Page directive doesn’t affect this property.
IsInAsyncPostBack Indicates whether the current page request is due to an asynchronous postback.
LoadScriptsBeforeUI Indicates whether scripts are loaded before or after markup for the page UI is loaded.
ProfileService Gets an object through which you can set preferences for the client-side profile service.
RoleService Gets an object through which you can set preferences for the client-side role service. This property is not available in ASP.NET AJAX Extensions for ASP.NET 2.0.
ScriptMode Gets and sets the type (debug or retail) of scripts to load when more than one type is available. Possible values come from the ScriptMode enumeration type: AutoInheritDebug, or Release. The default value is Auto, meaning that the type of script is determined on the fly.
ScriptPath Indicates that scripts should be loaded from this path instead of from assembly Web resources.
Scripts Gets a collection of script references that the ScriptManager control should include in the page.
Services Gets a collection of service references that the ScriptManager control should include in the page.
SupportsPartialRendering Indicates whether a particular browser or browser version can support partial page rendering. If this property is set to false, regardless of the value of the EnablePartialRendering property, no partial rendering will be supported on the page. The property is set to true by default.
 

The script manager is the nerve center of any ASP.NET AJAX pages and does all the work that is necessary to make AJAX features function as expected. Enabling AJAX features mostly means injecting the right piece of script in the right place. The script manager saves ASP.NET developers from dirtying their hands with JavaScript.

Methods of the ScriptManager Control

Table 2 lists the methods defined on the ScriptManager control.

Table 2. Methods of ScriptManager
Method Description
GetCurrent Static method, returns the instance of the ScriptManager control active on the current page.
GetRegisteredArrayDeclaration Static method, returns a read-only collection of ECMAScriptThis method is not available in ASP.NET AJAX Extensions for ASP.NET 2.0. array declarations that were previously registered with the page.
GetRegisteredClientScriptBlocks Static method, returns a read-only collection of client script blocks that were previously registered with the ScriptManagerThis method is not available in ASP.NET AJAX Extensions for ASP.NET 2.0. control.
GetRegisteredDisposeScripts Static method, returns a read-only collection of dispose scripts that were previously registered with the page. This method is not available in ASP.NET AJAX Extensions for ASP.NET 2.0.
GetRegisteredExpandoAttributes Static method, returns a read-only collection of custom (expando) attributes that were previously registered with the page. This method is not available in ASP.NET AJAX Extensions for ASP.NET 2.0.
GetRegisteredHiddenFields Static method, returns a read-only collection of hidden fields that were previously registered with the page. This method is not available in ASP.NET AJAX Extensions for ASP.NET 2.0.
GetRegisteredOnSubmitStatements Static method, returns a read-only collection of onsubmitThis method is not available in ASP.NET AJAX Extensions for ASP.NET 2.0. statements that were previously registered with the page.
GetRegisteredStartupScripts Static method, returns a read-only collection of startup scripts that were previously registered with the page. This method is not available in ASP.NET AJAX Extensions for ASP.NET 2.0.
RegisterArrayDeclaration Static method, ensures that an ECMAScript array is emitted in a partial rendering page.
RegisterAsyncPostBackControl Takes note that the specified control can trigger an asynchronous postback event from within an updatable panel.
RegisterClientScriptBlock Static method, ensures that the specified script is emitted in a partial rendering page.
RegisterClientScriptInclude Static method, ensures that the markup to import an external script file through the src attribute of the <script> tag is emitted in a partial rendering page.
RegisterClientScriptResource Static method, ensures that the markup to import an external script from the page’s resources is emitted in a partial rendering page.
RegisterDataItem Registers a string of data that will be sent to the client along with the output of a partially rendered page.
RegisterDispose Registers controls that require a client script to run at the end of an asynchronous postback to dispose of client resources.
RegisterExpandoAttribute Static method, ensures that the markup to import a custom, nonstandard attribute is emitted in a partial rendering page.
RegisterExtenderControl Registers an extender control with the current ASP.NET AJAX page.
RegisterHiddenField Static method, ensures that the specified hidden field is emitted in a partial rendering page.
RegisterOnSubmitStatement Static method, ensures that that client-side script associated with the form’s OnSubmit event is emitted in a partial rendering page.
RegisterPostBackControl Takes note that the specified control can trigger a full postback event from within an updatable panel.
RegisterScriptControl Registers a script control with the current ASP.NET AJAX page.
RegisterScriptDescriptors Registers a script descriptor with the current ASP.NET AJAX page.
RegisterStartupScript Static method, ensures that client-side script is emitted at the end of the <form> tag in a partial rendering page. In this way, the script will execute as the page refresh is completed.
SetFocus Allows you to move the input focus to the specified client element after an asynchronous postback.
 

All static methods emit some form of script and markup in the client page. These static methods are the AJAX counterpart of similar methods defined on the page’s ClientScript object that you should know from ASP.NET 2.0. The static RegisterXXX methods on the ScriptManager class ensure that the given piece of script and markup is properly emitted only once in each partial update of the ASP.NET AJAX page. Similarly, other nonstatic RegisterXXX methods should be seen as tools to emit proper script code in ASP.NET AJAX pages—especially script code that is associated with custom controls.

Note

Script registration is an old feature of ASP.NET, in spite of the slight changes that occurred in the transition from version 1.x to 2.0. To most developers, script registration is a pretty neat and clear feature. However, the advent of ASP.NET AJAX extensions mixed things up a little bit. What’s the difference between RegisterXXX methods in the ScriptManager control and the page’s ClientScript object, which is an instance of the ClientScriptManager class?

ClientScriptManager’s and ScriptManager’s registration methods serve the same purpose but in radically different scenarios. You need to use the ScriptManager’s methods only if you need to emit script code during an AJAX partial rendering postback operation. An AJAX partial rendering postback operation is processed by the runtime as usual, except for the rendering stage. At this time, the markup is generated and any registered script is emitted. Because during AJAX postbacks the ScriptManager is responsible for the markup rendering, it’s the ScriptManager that needs to know about registered scripts to emit. If you stick to using ClientScriptMananager’s methods in an AJAX page, you risk that no script will be emitted during the refresh of an updatable panel. As a result, a portion of your page might display strange and weird behaviors.

Events of the ScriptManager Control

Table 3 details the two events fired by the ScriptManager control.

Table 3. Events of ScriptManager
Event Description
AsyncPostBackError Occurs when an exception goes unhandled on the server during an asynchronous postback.
ResolveScriptReference Occurs when the ScriptManager control is going to resolve a script reference.
 

Both events are much more than mere notifications of something that has happened on the server. Both give you good chances to intervene effectively in the course of the application. For example, by handling the ResolveScriptReference event, you can change the location from where the script is going to be downloaded on the client:

protected void ResolveScript(object sender, ScriptReferenceEventArgs e)
{
    // Check Path or Name on the e.Script object based on what you've put in Scripts.
    // Next, you specify the real file to load
    if (String.Equals(e.Script.Path, "personal.js", StringComparison.OrdinalIgnoreCase))
         e.Script.Path = "person.js";
}


					    

By handling the AsyncPostBackError event, you can edit the error message being returned to the client. Here’s an example:

protected void AsyncPostBackError(object sender, AsyncPostBackErrorEventArgs e)
{
        ScriptManager sm = sender as ScriptManager;

        if (Request.UserHostAddress == "127.0.0.1")
        {
            sm.AsyncPostBackErrorMessage = String.Format(
                "<b>An error occurred. <br/>{0}<b>",
                e.Exception.Message);
        }
        else
        {
            sm.AsyncPostBackErrorMessage = String.Format(
                "<b>An error occurred. <br/>{0}<b>",
                "Please contact your Web master.");
        }
}


					    

When executed locally, the client-side error message appears as you see in Figure 1.

Figure 1. An error occurred during a partial rendering operation

 

What if you don’t like to display the message directly in the page popups and want to redirect the user to an error page instead? In this case, you configure the page to use the traditional error-handling mechanism for ASP.NET pages. You configure the <customErrors> section in the web.config file and indicate HTML error pages to reach in case of specific errors. This behavior is fully supported by ASP.NET AJAX and can be disabled by setting to false the value of the AllowCustomErrorRedirects property of the ScriptManager object.

Note

When an exception is thrown during a partial rendering operation, the HTTP request returns a regular HTTP 200 status code, but instead of including the updated markup, it includes a full description of the error. In ASP.NET AJAX Extensions for ASP.NET 2.0, the default error handler pops up a client-side message box with the exception message or any text you assign to the AsyncPostBackErrorMessage property. In ASP.NET 3.5, on the other hand, you get a JavaScript exception.


The ScriptManagerProxy Control

Only one instance of the ScriptManager control can be added to an ASP.NET AJAX page. However, there are two ways in which you can do this. You can add it directly on the page using the <asp:ScriptManager> tag or indirectly by importing a component that already contains a script manager. Typically, you can accomplish the second alternative by importing a user control, creating a content page for a master page, or authoring a nested master page.

What if a content page needs to add a new script reference to the manager? In this case, you need a reference to the script manager. Although it’s defined in the master page (or in a user control), the script manager might not be publicly exposed to the content page. You can use the static method GetCurrent on the class ScriptManager to get the right instance:

// Retrieve the instance of the ScriptManager active on the page
sm = ScriptManager.GetCurrent(this.Page);
 

The ScriptManagerProxy class saves you from this sort of coding. In general, in cases where you need features of the ScriptManager control but lack a direct reference to it, you can instead include a ScriptManagerProxy control in the content page.

You can’t have two script managers in the context of the same page; however, you can have a script manager and a proxy to retrieve it. The ScriptManagerProxy control enables you to add scripts and services to nested components, and it enables partial page updates in user controls and nested master pages. When you use the proxy, the Scripts and Services collections on the ScriptManager and ScriptManagerProxy controls are merged at runtime.

Note

The ScriptManagerProxy class is a very simple wrapper around the GetCurrent method of the ScriptManager class, and its programming interface is not an exact clone of the ScriptManager. From within the proxy, you have access only to a limited number of properties, including ScriptsServicesAuthenticationService, RoleService, and ProfileService. If you need to modify anything else, refer to the GetCurrent static method of the ScriptManager class.


Script Binding and Loading

By extensively relying on client capabilities, ASP.NET AJAX requires a lot of script code. The framework itself links a lot of code, as do custom controls and actual user pages. The only HTML-supported way of linking script files is the <script> tag and its src attribute. The ScriptManager control can be used to save yourself the direct manipulation of quite a few <script> tags and also obtain richer features, such as built-in management of localized scripts.

You use the Scripts collection to tell the ScriptManager about the scripts you want to add to the page. The collection can be accessed either declaratively or programmatically. In addition to the user-requested scripts, the ScriptManager control automatically emits in the client page any ASP.NET AJAX required script. This means that, as a page developer, you don’t have to worry about linking the Microsoft AJAX library or any other ASP.NET AJAX native feature. The following example illustrates the script loading model you can use to load optional and custom scripts:

<asp:ScriptManager runat="server" ID="ScriptManager1">
  <Scripts>
    <asp:ScriptReference
         Name="YourCompany.ScriptLibrary.CoolUI.js"
         Assembly="YourCompany.ScriptLib" />
    <asp:ScriptReference
         Path="~/Scripts/MyLib.js" />
  </Scripts>
</asp:ScriptManager>
 

Table 4 lists the properties of the ScriptReference class by means of which you can control the loading of scripts.

Table 4. Events of ScriptManager
Property Description
Assembly Indicates the assembly that contains in its resources the script to download on the client.
IgnoreScriptPath Boolean value, indicates whether the ScriptPath value optionally set at the top ScriptManager level has to be ignored. This property is set to false by default.
Name Name of the script to download on the client.
NotifyScriptLoaded Boolean value, indicates whether the script resource loader should automatically append a script-loaded notification statement to let the Sys.Application object know when the script is loaded. This property is set to true by default.
Path Indicates the server path where the script to download on the client can be found.
ResourceUICultures A comma-delimited string of valid user-interface cultures supported by the path.
ScriptMode Specifies the algorithm for choosing between the debug and release versions of the script file. If no debug version exists, the ScriptReference class automatically falls back to release code. Feasible values for the property come from the ScriptMode enumeration type.
 

You can reference script files, including ASP.NET AJAX system scripts, either from an assembly or from a disk file. There’s a benefit in using disk files. You gain something in performance because less work is required to load the script in memory directly from a file. We’ll see how to reference from disk the principal ASP.NET AJAX script file—MicrosoftAjax.js—which alone contains two-thirds of the Microsoft AJAX library. The technique is also valid for any custom script file, however.

Normally, you don’t take care of MicrosoftAjax.js—you just find it downloaded care of the script manager. If you examine the HTML source of an ASP.NET AJAX page, you can hardly find a reference to such a file. Here’s what you find instead:

<script src="/Core35/ScriptResource.axd?d=...&amp;t=..."
        type="text/javascript">
</script>
 

Script references obtained from embedded Web resources are served by the ScriptResource.axd HTTP handler. In ASP.NET AJAX, this handler replaces an old acquaintance, the WebResource.axd handler—a native component of ASP.NET 2.0. What’s the difference? In addition to serving script references, the ScriptResource.axd handler also appends any localized JavaScript resource types for the file.

To load a system file from disk, or to load a manually modified version of a system script file, you create a directory structure that roots under a custom folder the following subdirectories:

System.Web.Extensions\3.5.0.0
 

Now set the ScriptPath property on ScriptManager to a custom parent folder specific to your application. Say, you call it JS:

<asp:ScriptManager ID="ScriptManager1" runat="server" ScriptPath="~/JS" />
 

All of a sudden, the MicrosoftAjax.js script file is now referenced as shown here:

<script
    src="~/JS/System.Web.Extensions/3.5.0.0/MicrosoftAjax.js"
    type="text/javascript">
</script>
 

Needless to say, your pages will fail if no such script files can be found in the specified directory path.

Handling Debug and Release Script Files

One of the additional free services offered by ScriptManager that isn’t offered by the classic <script> tag is the ability to automatically link debug or release script files, as appropriate. ASP.NET uses a special naming convention to distinguish between debug and release script files. Given a release script file named script.js, its debug version is expected to be filed as script.debug.js.

In general, the main difference between debug and release scripts is that the release scripts remove unnecessary blank characters, comments, trace statements, and assertions. Normally the burden of switching the links to debug and release scripts is left to the developer.

The ScriptManager control takes on this burden and, based on the aforementioned naming convention, distinguishes between debug and release scripts. The ScriptManager control picks debug scripts when the debug attribute of the <compilation> section in the web.config file is true.

Localized Scripts

Script files can have localizable elements such as text strings for messages and user-interface elements. When the EnableScriptLocalization property is set to true and a UI culture is properly set in the page, the script manager automatically retrieves script files for the current culture, if any.

Localization is driven by the UICulture attribute in the @Page directive and the UICulture property in the Page class:

<%@ Page Language="C#" UICulture="it-IT" ... %>
 

This information is not enough for the ScriptManager to pick up localized scripts, if any. You also need to specify which UI cultures you intend to support for each referenced script. You indicate the supported cultures through the ResourceUICultures property on individual script references. The property is a comma-separated string of culture symbols. Here’s an example:

<asp:ScriptManager ID="ScriptManager1" runat="server" EnableScriptLocalization="true">
   <Scripts>
      <asp:ScriptReference Path="Person.js" ResourceUICultures="it-IT" />
   </Scripts>
</asp:ScriptManager>


					    
 

Note that ResourceUICultures is ignored if the Path attribute is not specified on the script reference tag.

At this point, if the page requires a script named person.js and the UI culture is set to it-IT, the ScriptManager object attempts to retrieve a script file named person.it-IT.js from the same path.

Script Globalization

Globalization is a programming feature that refers to the code’s ability to support multiple cultures. A request processed on the server has a number of ways to get and set the current culture settings. For example, you can use the Culture attribute on the @Page directive, the Culture property on the Page class, or perhaps the <globalization> section in the web.config file. How can you access the same information on the client from JavaScript?

When the EnableScriptGlobalization property is true, the ScriptManager emits proper script code that sets up a client-side global Sys.CultureInfo object that JavaScript classes can consume to display their contents in a culture-based way. Only a few methods and a few JavaScript objects support globalization. In particular, it will work for the localeFormat method of DateString, and Number types. Custom JavaScript classes, though, can be made global by simply calling into these methods or accepting a Sys.CultureInfo object in their signatures.