次の方法で共有


ASP.NET

ScriptManager Enables AJAX In Your Web Apps

Ben Rush

This article discusses:

  • The role of ScriptManager in ASP.NET AJAX
  • Implicit and explicit use of ScriptManager
  • Web services support in ASP.NET AJAX
  • The ASP.NET AJAX page lifecycle
This article uses the following technologies:
ASP.NET AJAX

Contents

Scripting with ScriptManager
AJAX and ScriptManager
Web Services and ScriptManager
Authentication and Personalization
How It All Works
Registering Objects with ScriptManager
Putting the AJAX in ASP.NET AJAX
Follow the Script

Today the consumer of a Web site can be as responsible for its content, direction, and success as the publisher. Social networking sites, blogs, online photo journals, and wikis are just a few of the new styles of Web sites springing up every day, and it's just the beginning. Your Web site can offer the best content available, but if it stands in the way of letting your users participate in its growth and development, your site will quickly fall into uselessness.

As a developer, it's your job to use the tools at your disposal to meet the demands of the average consumer. The most natural place to start when it comes to making a site more compelling is the Web browser itself, as it is the closest part of the site to the consumer. Unfortunately, it is not always easy to take advantage of many features found in current Web browsers. Writing code that interacts with the browser requires a significant effort because myriad browser and operating system combinations exist, making consistency painfully absent. It would help to have a single platform for building rich client and Web applications in parallel so that work spent perfecting the end-user experience for one browser environment doesn't need to be duplicated for another.

ASP.NET AJAX was released by Microsoft to answer this need in Web application development. My goal in writing this article is to expand your knowledge of a central component of ASP.NET AJAX called the ScriptManager control and to show how advanced programming of ASP.NET AJAX is achieved using it. ScriptManager is a server-side control that sits on your Web Form and enables the core of ASP.NET AJAX. Its primary role is the arbitration of all other ASP.NET AJAX controls on the Web Form and the addition of the right scripting libraries to the Web browser so that the client portion of ASP.NET AJAX can function. Often you will find yourself using the ScriptManager to register other controls, Web services, and client scripts.

As a server-side control, ScriptManager reacts to events in the ASP.NET page lifecycle and uses those events to coordinate the activities of all the controls, options, and code employed by ASP.NET AJAX. ScriptManager will hook a particular event, get notified when it occurs, and configure a few settings depending on the environment; this process will repeat itself several times through the rendering cycle of your ASP.NET page. The settings it configures, however, are often the exact settings needed to make your use of ASP.NET AJAX seamless.

I will first go over a few of the main features of ASP.NET AJAX that the ScriptManager control enables for you, and then begin an exploration of the lifecycle of the control on the server. By understanding the internals of ScriptManager, you will gain a deeper appreciation for the options it provides for Web application development and learn how to get the most out of it.

Let's start with scripting, as it is a central element to ASP.NET AJAX. In fact, all of the functionality of ASP.NET AJAX depends upon its scripting libraries. I will then run through some features of AJAX support in ASP.NET AJAX, how to interact with Web services, and finally talk a bit about authentication. During each discussion I'll show you how to tweak options through ScriptManager.

Scripting with ScriptManager

The block of code in Figure 1 demonstrates the standard way of defining a class in ASP.NET AJAX. The internals of the client script library are beyond the scope of this article, but in summary the common steps necessary to create a class based on the ASP.NET AJAX script extensions are as follows:

  1. Register the namespace with ASP.NET AJAX.
  2. Create a constructor method.
  3. Create the class prototype by filling in the member methods and their functionality.
  4. Register the class with ASP.NET AJAX.
  5. Notify the client script added by the ScriptManager that you have reached the end of the type definition (the call to Sys.Application.notifyScriptLoaded).

This class exposes functionality to the client alone. However, through the ScriptManager control you can take advantage of a much more interesting side of scripting in ASP.NET AJAX where your class can expose functionality to both JavaScript on the client and Microsoft® .NET Framework code on the server. For example, if a consumer of your control was to set a property on an instance of it like this

public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { this.MyCustomButton.AlertMessage = "Hey, stop clicking me!"; } }

it would be intuitive to also be able to interact with that control instance through script in the browser like this:

<script type="text/javascript"> function get_text() { var mb = $find("MyCustomButton"); var text = mb.get_AlertMessage(); // do something with the text } </script>

Note that I use the same name, MyCustomButton, as a reference to the control on both the server and the client; I also interact with the property, AlertMessage, as if its value seamlessly crossed the server/client boundary. This feels natural, but until ASP.NET AJAX this type of unified server/client programming model was not easy to access and would have required a great deal of custom code. Today, in ASP.NET AJAX, this unified server/client paradigm is integrated and fully supported.

The magic of this tight server/client integration comes through two new interfaces, IScriptControl and IExtenderControl. To use these interfaces, you inherit your ASP.NET Web control from them, implement the required interface methods, and then register the control with the page's ScriptManager control. For example, the following skeleton of server-side control code implements the required methods of IScriptControl:

class CustomButton : Button, IScriptControl { IEnumerable<ScriptReference> IScriptControl.GetScriptReferences() { ... } IEnumerable<ScriptDescriptor> IScriptControl.GetScriptDescriptors() { ... } }

The methods GetScriptDescriptors and GetScriptReferences will return to the ScriptManager control all the information it needs to logically present your control code as a server and client object. The end result will be an experience like you saw previously, where object instances transcend the server/client boundary.

GetScriptReferences will return a list of script files your control code will need. One of the script files it must return contains the definition for your control as a script class—in other words, the client script version of your server-side control.

GetScriptDescriptors, on the other hand, returns what are known as ScriptDescriptors, or objects that describe the properties and events of your client class. ScriptDescriptors can be seen as holding onto metadata for your client class, including properties and their associated values.

Figure 2 shows a more complete example of the server control sketched out earlier. Here you can see the bodies of the GetScriptDescriptors and GetScriptReferences methods filled in.

Figure 2 Using GetScriptDescriptors and GetScriptReferences

private string _alertMessage = null; public string AlertMessage { get { return _alertMessage; } set { alertMessage = value; } } public IEnumerable<ScriptDescriptor> GetScriptDescriptors() { ScriptControlDescriptor descriptor = new ScriptControlDescriptor( "CustomButton", this.ClientID); descriptor.AddProperty("AlertMessage", this._alertMessage); return new ScriptDescriptor[] { descriptor }; } public IEnumerable<ScriptReference> GetScriptReferences() { ScriptReference reference = new ScriptReference( "MyCustomContent.JScript1.js", "MyCustomContent"); return new ScriptReference[] { reference }; }

In the body for GetScriptDescriptors, I instantiate a ScriptDescriptor object. The constructor for the ScriptDescriptor takes the name for the control I am describing, CustomButton. I then add the AlertMessage property and its value (held by the private member _alertMessage) to ScriptDescriptor. The property that I want to interact with from the client code, AlertMessage, has now been described to ASP.NET AJAX.

In the body of GetScriptReferences, notice that I am creating and returning a ScriptReference object pointing to a .js file. This .js file maintains the client-side code for that control—and thus all its client functionality.

Now I can register the control with the ScriptManager so that it is aware the control exists (I'll explore that step later). When the page is rendered, the ScriptManager control will react to various events for which it gets notified and call GetScriptDescriptors and GetScriptReferences. These two methods will then return to the ScriptManager control ScriptDescriptors and ScriptReferences containing all the necessary information to hook up the client object to its server counterpart. In the example here, ScriptDescriptor describes a custom control named CustomButton with a property AlertMessage. Also included in the ScriptDescriptor will be the value of the property set from within GetScriptDescriptors.

The ScriptManager control will also get a reference to an external .js file called MyCustomContent.JSScript1.js. The reference to the script file will be in the form of a ScriptReference object returned from GetScriptReferences. The prototype and footer for the control's client class in the .js file is defined as shown in Figure 3.

Figure 3 Prototype for the Control Client

CustomButton.prototype = { initialize : function() { // initialize the base CustomButton.callBaseMethod(this,'initialize'); this._onclick = Function.createDelegate(this, this._onclick); Sys.UI.DomEvent.addHandler( this.get_element(), 'click', this._onclick); }, dispose : function() { // release the handlers $clearHandlers(this.get_element()); // call to the base to do its dispose CustomButton.callBaseMethod(this,'dispose'); }, get_AlertMessage: function(){ return this._alertMessage; }, set_AlertMessage: function(value){ this._alertMessage = value; return; }, _onclick: function(e){ alert(this._alertMessage); return; } } CustomButton.registerClass("CustomButton",Sys.UI.Control); if(typeof(Sys)!=='undefined') Sys.Application.notifyScriptLoaded();

The first thing you should notice is that this client class has get_ and set_ methods for the AlertMessage property. The ScriptManager control will set the value of this property to whatever you specified on the server through the ScriptDescriptor object. Since I exposed AlertMessage from the server code, whatever that property was set to on the server will now be reflected in the AlertMessage property exposed on the client object.

AJAX and ScriptManager

Many developers, when first using ASP.NET AJAX, start with the UpdatePanel control. If a ScriptManager control is included on the page and the UpdatePanel contains any controls, the controls within UpdatePanel can be asynchronously updated through the facilities of AJAX. On the designer surface of Visual Studio®, the setup typically looks like Figure 4.

Figure 4 A Simple Asynchronous Control

Figure 4** A Simple Asynchronous Control **

It really could not get much more elementary than this. If someone clicks the Button in this setup, the Button control will raise a postback event that will be caught by the UpdatePanel control. The UpdatePanel then resubmits the postback event as a partial postback and its content will be asynchronously updated (without the browser fully reloading the page).

However, there are many interesting scenarios where your expectations will fail, leaving you with a mess on your hands. For example, there is a new and fascinating way to break script references using the UpdatePanel control and AJAX. In days of old you would have added script to the page as a result of a full postback such as this:

protected void Button1_Click(object sender, EventArgs e) { Page.ClientScript.RegisterStartupScript( this.GetType(),"myscript","alert('hello world!');"); }

But this new method can break under ASP.NET AJAX. Why? Because ClientScriptManager is not a control that understands partial postbacks and partial rendering, and so script code registered with it will not be included in the page response for a partial postback.

Instead, if you wanted the button to introduce client script into the page output, you would leverage the ScriptManager:

protected void Button1_Click(object sender, EventArgs e) { ScriptManager.RegisterStartupScript( this,this.GetType(),"myscript","alert('hello world!');",true); }

The result is the same, but the method is slightly different. In fact, the methods of the ClientScriptManager control are now included in the ScriptManager control as static methods (RegisterStartupScript, for example). Whenever you are utilizing ASP.NET AJAX and want to work with script through partial postbacks, you must now use methods exposed by the ScriptManager control instead.

As another advanced case scenario, consider the control setup in Figure 5. Under normal circumstance the LinkButton, once clicked, would cause a full postback of the Web Form. But what if you wanted to click the LinkButton and have it refresh the UpdatePanel asynchronously? In other words, you want to click the LinkButton and make the UpdatePanel behave as if the LinkButton were inside it. To do this, use a method on the ScriptManager control called RegisterAsyncPostBackControl:

Figure 5 Using Link­Button Asynchronously

Figure 5** Using Link­Button Asynchronously **

protected void Page_Load(object sender, EventArgs e) { // register the LinkButton1 control as one that can // cause partial postbacks. ScriptManager1.RegisterAsyncPostBackControl(LinkButton1); }

Clicking the LinkButton will now cause the UpdatePanel to refresh asynchronously, just as if the LinkButton control were actually in the UpdatePanel.

As an example of inverse behavior, you could also make an element inside an UpdatePanel cause the whole page to refresh—a full postback. To do so, you just use a different method on the ScriptManager called RegisterPostBackControl:

protected void Page_Load(object sender, EventArgs e) { // Button1 will cause a full post-back no matter // where it is on the page. ScriptManager1.RegisterPostBackControl(Button1); }

This code will cause the control, Button1, to do a full postback of the page, even if Button1 exists within an UpdatePanel.

Let's go even further now. You have seen how to tweak the controls that trigger the UpdatePanel control's partial postback event using ScriptManager, but what if you wanted to update a control somewhere on the page—totally outside of the UpdatePanel—when the UpdatePanel refreshes? This is also possible through the ScriptManager control.

ScriptManager's RegisterDataItem method makes it easy to refresh controls or data outside an UpdatePanel. RegisterDataItem allows extra data of your choosing to be sent down to the client when an UpdatePanel control posts back; the data can be consumed by script you write on the client.

For example, say you have a situation like the controls in Figure 6. In this example, I want to update the Label with whatever value has been clicked in the Calendar control. This may seem simple, but the Calendar control is within the UpdatePanel while the Label is not. How can I get the UpdatePanel to refresh the Label? The answer is simple: if I use RegisterDataItem from my server-side code, I can get extra data sent to my client-side code. The client can consume the data sent from RegisterDataItem and refresh the Label with it:

Figure 6 Updating Controls Outside of an UpdatePanel

Figure 6** Updating Controls Outside of an UpdatePanel **

protected void Calendar1_SelectionChanged(object sender, EventArgs e) { ScriptManager1.RegisterDataItem( Label1, Calendar1.SelectedDate.ToShortDateString()); }

RegisterDataItem takes the control you are planning to update as the first argument and the raw data you wish to update the control with as the second argument. The ScriptManager control will take the data you pass, wrap it up, and send it down to the client as part of its response to the partial postback event. In your client code, you would retrieve the data sent from the ScriptManager control after the event has completed, like this:

<script type="text/javascript"> Sys.WebForms.PageRequestManager.getInstance().add_pageLoading( PageLoadingHandler); function PageLoadingHandler(sender,args){ var dataItems = args.get_dataItems(); if($get('Label1')!==null){ $get('Label1').innerHTML = dataItems['Label1']; } return; } </script>

Look at the script code. It does a number of things. It registers for the pageLoading event through the PageRequestManager client class. Next, it implements the event handler for the pageLoading event, PageLoadingHandler. It gets a name/value collection of data items from the second parameter, args. Finally, it retrieves the value you want using the name of the control you supplied as the first argument to RegisterDataItem on the server.

Web Services and ScriptManager

The ease with which you can now asynchronously interact with Web services from script in ASP.NET AJAX, handle the response (including errors), and process the response gives you a great deal of power to really make your pages useful and unique. From point to point—browser to server—you are utilizing the latest technologies of the new Web in one of the most intuitive manners possible through ASP.NET AJAX.

The ScriptManager control acts as a global registrar of sorts for all the services you need in your ASP.NET AJAX application. Figure 7 shows the properties menu for the ScriptManager control as seen through Visual Studio.

Figure 7 ScriptManager Properties

Figure 7** ScriptManager Properties **

You can see that I have the Scripts collection highlighted, and below that is a collection for Services (Web services) as well. You need to register with ScriptManager any services you want to interact with from your client code. To add a service reference to the ScriptManager control, you would simply expand the Services collection and add a reference as shown in Figure 8.

Figure 8 Adding a Web Service Reference

Figure 8** Adding a Web Service Reference **(Click the image for a larger view)

What exactly does this do? The complete details will be spelled out shortly, but if you were to view the source for a page referencing a service through the ScriptManager, you might see something like this in its content:

<script src="WebService.asmx/jsdebug" type="text/javascript"></script>

This external reference was added to the page output by ScriptManager because the Web service is registered with it. Note that /jsdebug is appended to the name of the Web service; if this were a release build, there would only be /js appended to it. When the ASP.NET AJAX pipeline sees a request for this service with the /js text appended, it will return a script class wrapping the methods of the Web service (this is known as proxy script). The body of each method on this proxy script class will perform an asynchronous call to its corresponding Web service method. Therefore, to call the Web service methods, you simply call the corresponding method on the script class built for you by the ASP.NET AJAX Web service framework. It's really that easy.

For example, if a service named WebService exposed a method called Add, like this

[WebMethod] public int Add(int a, int b) { return a + b; }

you could call it from this script:

function CallAdd() { // method will return immediately // processing done asynchronously WebService.Add(0,6, OnMethodSucceeded, OnMethodFailed); }

In this example, the auto-generated script proxy class handed to you by the framework wraps the Web service method calls so that you may interact with it from script. WebService.Add will thus call the corresponding Web service method, Add.

Two callbacks are typically defined when calling into a Web service: one for a success case and another for a failure case. Since the Web service call is executed asynchronously, callbacks are required to know how the call actually completed. For example, the following methods implement a success callback and failure callback, respectively:

function OnMethodSucceeded(result, eventArgs) { var label = Sys.UI.DomElement.getElementById('Label1'); var sb = new Sys.StringBuilder(); sb.append('You got back a value of '); sb.append(result.toString()); label.innerHTML = sb.toString(); } function OnMethodFailed(error) { alert(error.get_message()); }

The request is sent and the result is received asynchronously from the Web service so the end effect is similar to using the UpdatePanels control. Content can be refreshed with the data received from the Web service without a full browser postback. In the sample code shown earlier, I asynchronously update Label1 with the result of the Web service call to WebMethod Add. The end result might look like Figure 9. No full postback will occur and the end result will be absolutely seamless.

Figure 9 The Updated Label

Figure 9** The Updated Label **

Authentication and Personalization

Advanced features of ASP.NET AJAX, such as authentication and personalization, use Web services, too. The authentication service in ASP.NET AJAX implements two methods: one for logging a user in and one for logging the user out:

Sys.Services.AuthenticationService.login Sys.Services.AuthenticationService.logout

ASP.NET AJAX employs forms authentication, meaning that the user is logged in when a valid forms authentication cookie is inserted into the browser's session by the Web server. The manner by which the cookie data is inserted into the session in ASP.NET AJAX is the same as it would be via a full postback, so physically there is no difference in the mechanisms being used. Once the cookie has already been inserted into the browser's session, the client is then authenticated with the server and can view restricted pages and content.

Being able to authenticate and log in or log out a particular user from client script yields interesting possibilities for highly interactive user-based systems. An example of logging in a user through AuthenticationService and script might look like this:

<script type="text/javascript"> function MyMethod(username, password) { Sys.Services.AuthenticationService.login(username, password,false,null,null,null,null,"User Context"); } </script>

The steps for enabling authentication in your AJAX application are well documented (see ajax.asp.net), so they needn't be covered here. But as you can clearly see, once it is enabled, forms authentication is easily implemented through script.

When the standard ASP.NET authentication service just doesn't fit the bill, you can create your own and register it with the ScriptManager control. The methods required by a Web service implementing authentication functionality are shown here:

[WebMethod] public bool Login(string userName, string password, bool createPersistentCookie) { ... // code to check user credentials and log user in } [WebMethod] public void Logout() { ... // code to log user out. }

After you have filled in your implementation code, you must register your new authentication service with the ScriptManager control. With your new authentication service registered, any code you previously had on the client interacting with the default authentication services of ASP.NET AJAX will now utilize your service instead. Therefore, from the client script side, no change will be required to use your own custom authentication Web service.

Here's an example of registering as an authentication service through ScriptManager declaratively:

<asp:ScriptManager ID="ScriptManager1" runat="server" > <AuthenticationService Path="WebService.asmx" /> </asp:ScriptManager>

Without going into the implementation at this point, you may also take advantage of your own profile services in ASP.NET AJAX. A profile service can be registered in the same way through the ScriptManager control as an authentication service. The profile services give you the ability to tailor a Web site to a particular user. Since it's done from client script, the effect to the user will be intuitive and seamless. Examples are available through ajax.asp.net.

How It All Works

The ScriptManager control has two basic phases to its existence. In the first phase it verifies that the environment can support all the rich features of ASP.NET AJAX and sets up all the necessary pieces to support it. In the second phase, it actually performs asynchronous communication with the code running on the client so that the script can perform the necessary page updates. Because it is a server-side control and Web programming in ASP.NET is event-driven, the core of the ScriptManager control is in how it registers for and responds to events. An overview of the events to which it responds is shown in Figure 10.

Figure 10 Defining a Class in ASP.NET AJAX

Figure 10** Defining a Class in ASP.NET AJAX **

Registering Objects with ScriptManager

You may add script and service references declaratively with ScriptManager. However, you may also add them programmatically through the Services and Scripts properties on the ScriptManager control. You also can choose special data to be sent to the client, and you can design controls to work with ScriptManager as special script controls. All of these features require that you reference your object with the ScriptManager control.

So what is ScriptManager doing with all of these references? When a page hosting a ScriptManager control and ASP.NET AJAX functionality is first requested by a browser, the initialization stage of the page's child controls will invoke ScriptManager's OnInit method. OnInit carries out a number of important steps, including a check that only one ScriptManager control resides on the page and whether the page is currently undergoing a partial postback. The most important step OnInit performs, however, is to register for a series of events generated by the host page such as InitComplete, PreRenderComplete, and PreRender. The ScriptManager control needs to know when these events occur on its parent page because ASP.NET AJAX operates by tapping into these page events.

The most crucial page event for loading script and service references into the browser is the page's PreRenderComplete event. The handler for this event is called OnPagePreRenderComplete (see Figure 11) and it is a method of the ScriptManager control itself.

Figure 11 ScriptManager's OnPagePreRenderComplete

private void OnPagePreRenderComplete(object sender, EventArgs e) { if (!IsInAsyncPostBack) { if (SupportsPartialRendering) { IPage.ClientScript.GetPostBackEventReference( new PostBackOptions(this, null, null, false, false, false, false, true, null)); } RegisterGlobalizationScriptBlock(); RegisterScripts(); RegisterServices(); } else RegisterScripts(); }

OnPagePreRenderComplete exists to process all the scripts and services that you registered with the ScriptManager control up to the PreRenderComplete event of the page (including scripts needed by script controls, scripts referenced by ScriptManagerProxy controls, and so on). If the page is not undergoing a partial postback, a globalization script block is registered, along with all your scripts and services. If, on the other hand, a partial postback is underway, only scripts are registered. Scripts need to be registered for both partial and full postbacks because the ScriptManager control offers the ability to include script at any time.

All of your scripts and services are registered during this phase, but what does that mean? How is the registration of the script or reference translated into some sort of output onto the page?

Remember that you are still processing the first page request and so you're not dealing with partial postbacks. Because of this, you can still use ClientScriptManager to register scripts. RegisterScripts, for example, iterates every script that was registered with the ScriptManager control and then hands it off to the page's default ClientScriptManager instance. When the page is rendered, ClientScriptManager will be called and the script references will be added to the page output, just like in pre-ASP.NET AJAX days.

RegisterScripts will be called during an asynchronous postback as well (due to the else clause shown in Figure 11). The method still calls into ClientScriptManager, but since ClientScriptManager won't know what to do in an asynchronous postback, nothing will happen. Instead, RegisterScripts will call an internal method, RegisterScriptIncludeInternal, that will tuck away the script reference in an internal array for later use.

What about Web service references? Recall that by adding a Web service reference to your ScriptManager control, a reference to script will be added to the page. This script reference will cause the browser to make a request at the specified URL. The ASP.NET AJAX framework will inspect the class implementing your Web service and return a proxy script class you may consume from client code. The browser will download this automatically generated proxy class, and this will be your Web service reference.

To generate the script reference to the auto-generated proxy class, RegisterScripts will iterate over every Web service reference you have and eventually call into the following method:

private string GetProxyPath(Control containingControl, bool debug) { if (debug) return GetServicePath(containingControl, true) + RestHandlerFactory.ClientDebugProxyRequestPathInfo; else return GetServicePath(containingControl, true) + RestHandlerFactory.ClientProxyRequestPathInfo; }

The script reference generated by GetProxyPath will be added to the page in the same manner as regular script references: by utilizing the ClientScriptManager for the first page request and by "tucking away" the reference into some internal array.

What is this internal array? It's a number of arrays, actually. Recall that for the first page request, the ScriptManager leans on the traditional method of simply adding script and service references to the ClientScriptManager class for the page. But using ClientScriptManager will not work during a partial postback. Instead, the ScriptManager must cook up some other method, and this is what the internal arrays are for. The response for a partial postback, as you will see, is actually a well-formed, easily parsable block of data that the client framework will inspect and consume. The ScriptManager control's Render event will be called and it, in turn, will call a member method, ProcessScriptRegistration, of yet another class, PageRequestManager (see Figure 12).

Figure 12 PageRequestManager's ProcessScriptRegistration

private void ProcessScriptRegistration(HtmlTextWriter writer) { owner.ScriptRegistration.RenderActiveArrayDeclarations( updatePanelsToRefresh, writer); owner.ScriptRegistration.RenderActiveScripts( updatePanelsToRefresh, writer); owner.ScriptRegistration.RenderActiveSubmitStatements( updatePanelsToRefresh, writer); owner.ScriptRegistration.RenderActiveExpandos( updatePanelsToRefresh, writer); owner.ScriptRegistration.RenderActiveHiddenFields( updatePanelsToRefresh, writer); owner.ScriptRegistration.RenderActiveScriptDisposes( updatePanelsToRefresh, writer); }

This is where the script reference will be rendered to the page output for ASP.NET AJAX. Each method will take the HtmlTextWriter handed to it for the Render phase and write its necessary content to it. Each method (for example, RenderActiveScripts) will refer to each respective internal array that was filled previously.

Putting the AJAX in ASP.NET AJAX

If you use the Fiddler HTTP debugger proxy app (fiddlertool.com/fiddler), you can trace all Web traffic that moves through Internet Explorer® on your machine. Browse to the AJAX samples on ajax.asp.net and watch what happens through Fiddler; you can see that an actual HTTP POST occurs each time you invoke a partial postback event. The HTTP POST contains the usual HTTP headers, but it also includes one that you may have never seen before:

x-microsoftajax: Delta=true

This header is key. Once the ScriptManager control identifies this header, instead of passively injecting references into the page output, it will inspect the data sent in the form POST and render a response back to the client in a format the client script understands.

Along with writing service and script references to the initial page output, the ScriptManager control will also initialize the client-side functionality. When it renders the first time, the ScriptManager control makes sure that two startup scripts are registered with the ClientScriptManager. One script calls into a method that initializes the client-side runtime. The other initializes the client-side version of the PageRequestManager class discussed earlier. For an understanding of AJAX, the second initialization script—the one that initializes PageRequestManager—is the most important.

This client script is written to the page output through a method called RenderPageRequestManagerScript. I removed a lot of the plumbing code from the method so it's easier to read, but in general it looks like this:

internal void RenderPageRequestManagerScript(HtmlTextWriter writer) { ... writer.Write(_owner.UniqueID); ... writer.Write(_owner.IPage.Form.ClientID); ... RenderUpdatePanelIDsFromList(writer, _allUpdatePanels); ... writer.Write(GetAsyncPostBackControlIDs(true)); ... writer.Write(GetPostBackControlIDs(true)); ... }

Notice how it is writing out UpdatePanels, PostBackControl IDs, and AsyncPostBackControl IDs to the page; these are all controls that need to take part in the ASP.NET AJAX experience. The client-side PageRequestManager is responsible for tracking all events generated by the controls registered with ScriptManager; the RenderPageRequestManagerScript method is what initializes the PageRequestManager operating on the client with the exact controls it should watch.

When a postback event is fired on the client, PageRequestManager will identify whether it was caused by any of the controls identified by the script written in RenderPageRequestManagerScript. If it was, PageRequestManager will cancel the postback event and repackage it. The newly packaged data from the postback event will then be transmitted to the server using the client class Sys.Net.WebRequest (which is public and can be used from your client-side code). The header x-microsoftajax: Delta=true will be set in the POST request sent to the server through Sys.Net.WebRequest.

On the server side, the ScriptManager control has now been instantiated and loaded into the control tree of the page. ScriptManager is a server-side control and so will be notified of the data in the form POST through LoadPostData, a method of ASP.NET that lets individual controls filter through the form POST for pertinent information. (Note that this is a standard event not specific to ASP.NET AJAX.) If you refer to the event diagram in Figure 10, you'll see that LoadPostData occurs immediately after InitComplete on the page. Within LoadPostData, the ScriptManager control identifies the controls that caused the form POST (including the UpdatePanel the control resided in, if applicable). The identity of the control that caused the postback event is tucked away for later use.

So far the ScriptManager control recognizes that a partial postback is occurring and has identified the controls that caused the postback. Now you can build the response to the client. The ScriptManager control completely overwrites the default Render method for its host page, taking over the rendering of your ASP.NET page. Realize that, up until now, you have simply been seeing the ScriptManager control as something to tweak various settings in your ASP.NET AJAX application. Now you can finally see that you give it these various options because it, arguably, becomes your new page class, complete with its own format for rendering controls to the page output stream.

In fact, ScriptManager will see to it that the default rendering for two objects are overwritten: first for the Page itself, and then for the Web Form on the page. Therefore, when the ASP.NET page framework asks the page to render, the ScriptManager's own internal implementation will be called. By knowing what controls caused the postback and their associations—is it in an UpdatePanel with other controls, or is the source of the postback event somehow linked to an UpdatePanel?—the ScriptManager control knows which child controls should be asked to render and which should be ignored. Where the default behavior for a Page class in ASP.NET is to ask each child control to render, ScriptManager will only ask those it deems necessary.

It is also worth noting that within the override for the Form objects' rendering, the ScriptManager will process and send down to the client all of the extra data you registered with it via the RegisterDataItem method. Therefore, it is important that you have your data ready to be sent down to the client by the time the Form object is asked to render itself to the client. The data to be sent down to the client will be encoded in a raw or JavaScript Object Notation (JSON) serialized format.

Finally, the client framework gets the asynchronous response from the server and parses out data. The ScriptManager control has packed into the response all the control IDs and new markup so the client framework can simply perform scripting operations on the browser's document object model to update the page content. Given that this whole process occurred asynchronously, the browser is quickly and silently updated and the user of the Web page gains a better experience.

Follow the Script

ASP.NET AJAX is a powerful technology. To harness many of the ASP.NET AJAX features in your Web app, you will need to employ the ScriptManager control as I've demonstrated in this article.

The ScriptManager control handles many details of the ASP.NET AJAX implementation for you. By now you should be noticing that the ScriptManager's presence can be felt frequently in instances where the default behavior of a control such as the UpdatePanel isn't exactly what you need. In addition to scripting features and AJAX, the ScriptManager control also supports such high-end features as authentication and personalization.

Scripts and services must be registered by the page's PreRenderComplete event with the ScriptManager control. You may add a script reference to the page during either full or partial postbacks. Other objects are asked to hand over to the real ScriptManager all of their script and service references when scripts are being registered.

Finally, ASP.NET AJAX is implemented on the client by overriding the default rendering implementation for its host page. The ScriptManager will take control of the page rendering cycle from then on and will send down to the client a response it can easily parse through and use to update elements within the browser. By overriding the default page rendering, the right controls can be rendered to the client in a specialized format, including with it data registered with the ScriptManager.

Ben Rush is a Microsoft .NET consultant in the Midwest specializing in ASP.NET technologies.