A dissection of the ASP.NET AJAX library – Part 3: The PageRequestManager on the client side

This is the third part in a series of articles in which I intend to dissect the ASP.NET Ajax library. I’m relative new to this modern type of javascript programming so I decided to look into the ASP.NET Ajax library to see how certain things are done. This is a report of my voyage.

HTML Forms, control events and submitting data.

What is the problem?

In a standard HTML form, if you click on the forms’ controls nothing happens. The only time that data is send to the server is when the submit button is pressed. At that time your browser will collect the data from the controls on the form and submit them to the server. Then the server can query the data send and process it.

With ASP.NET, most of this still is true. What changes is that you are given the chance to respond to control changes of the controls on your form. To be able to do this ASP.NET attaches a call to the __doPostBack(eventTarget, eventArgument) function to the client side event of the control.

<script type=”text/javascript”>

<!–

function __doPostBack(eventTarget, eventArgument) {

    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {

        theForm.__EVENTTARGET.value = eventTarget;

        theForm.__EVENTARGUMENT.value = eventArgument;

        theForm.submit();

    }

}

// –>

</script>

<div>

    <input type=”submit” name=”Button1″ value=”Button” id=”Button1″ />

    <select name=”DropDownList1″

        onchange=”javascript:setTimeout(‘__doPostBack(\’DropDownList1\’,\’\’)’, 0)”

        id=”DropDownList1″>

    <option value=”t1″>t1</option>

    <option selected=”selected” value=”t2″>t2</option>

    <option value=”t3″>t3</option>

</div>

And as you can see from the above code the __doPostBack function takes some information from the control triggering the call and submits the form. On the serverside the information is analysed by ASP.NET and converted to the event of the serverside control being triggered.

But as you can see from the code the form is still completely submitted to the server. So responding to every click on the form is not an option and that is why ASP.NET connects calls to the __doPostBack function only for those events you are interested in. And you tell ASP.NET you are interested in an event by declaring an eventhandler in the code-behind-file of your ASP.NET page:

protected void RadioButton1_CheckedChanged(object sender, EventArgs e)

{

    TextBox1.Text = “My Text”;

}

How to circumvent total page submitions: XmlHttpRequest

I’m sure you didn’t come to this page without having heard of the XmlHttpRequest object. After all, this object is the heart of Ajax.

What this browser object allows us to do is to submit a HTTP request without actually requesting a complete new page.

<script type=”text/javascript”>

function MakeRequest()

{

    var requestUrl = “XMLHttpRequestResponse.aspx”;

    XmlHttp = new ActiveXObject(“Msxml2.XMLHTTP”);

    if(XmlHttp)

    {

        XmlHttp.onreadystatechange = HandleResponse;

        XmlHttp.open(“GET”, requestUrl,  true);

        XmlHttp.send(null);          

    }

}

 

function HandleResponse()

{

    if(XmlHttp.readyState == 4)

    {

        document.getElementById(“theDiv”).innerText = XmlHttp.responseText;

    }

}

</script>

<div id=”theDiv”><button onclick=”MakeRequest()”>Press me!</button></div>

First, XmlHttpRequest is an object which is supported only by a number of browsers and even those who implement it do it in a different way. The code above shows the Internet Explorer way of doing things.

So we create a XmlHttpRequest object and attach to it an event handler for the onreadystatechange event which is triggered each time the object changes it state. The object goes through 5 states which are represented by the readyState property:

  1. 0 = uninitialized
  2. 1 = open
  3. 2 = sent
  4. 3 = receiving
  5. 4 = loaded

After setting the event handler for the state changes, we call the open method and specify the URL we want to retrieve.
Finally we send the request by calling the send() method.

After this is done, on each state change our eventhandler is called. We are interested in the state 4: the loaded state which indicates that our call has been submitted and the answer is completely received.

Finally, we replace the text of the DIV element with the received text.

In the page that we requested through the XmlHttpRequest, we construct the response:

protected void Page_Load(object sender, EventArgs e)

{

    Response.Clear();

    Response.ContentType = “text/xml”;

    Response.Write(“Hi from the XMLHttpRequestResponse page”);

    Response.End();

}

This is simple enough: clear the response buffer and fill it with our response.

If you try these pages you will see that your button gets replaced with the text submitted by the ajax response page.

Posting form changes without total page submition: the forms’ onclick event

So, what we would like is be able to post changes in the form without the need to submit the complete form but just the changes. For this we can use the forms onclick event. When you attach a handler to the forms’ onclick event that handler will get called each time you, well, click the form and this means any contol on the form. This is exactly what ASP.NET Ajax does with the PageRequestManager class.

The PageRequestManager class: the heart of ASP.NET Ajax

Right, so we design a page with ASP.NET Ajax:

  • Make a new ASP.NET page
  • Put a ScriptManager object on it
  • Put an UpdatePanel object on it
  • Add some controls to the UpdatePanel
  • Attach some eventhandlers to the controls

Now, if you run this page you will see that your eventhandlers get called without neediing total page submitions.
What happened here?

Initialize the page

If you look at the source of the generated page (that is in the browser, not in your development environment) you will see, among other things, the following:

<div>

<script type=”text/javascript”>

//<![CDATA[

Sys.WebForms.PageRequestManager._initialize(‘ScriptManager1’, document.getElementById(‘form1’));

Sys.WebForms.PageRequestManager.getInstance()._updateControls([‘tUpdatePanel1’], [], [], 90);

//]]>

</script>

</div>

<div id=”UpdatePanel1″>

 

<input id=”RadioButton1″ type=”radio” name=”RadioButton1″ value=”RadioButton1″ />

<input type=”submit” name=”Button1″ value=”Button” id=”Button1″ />

<select name=”DropDownList1″ id=”DropDownList1″>

    <option value=”t1″>t1</option>

    <option value=”t2″>t2</option>

    <option value=”t3″>t3</option>

</select>

<input name=”TextBox1″ type=”text” id=”TextBox1″ />

 

</div>

 

 

<script type=”text/javascript”>

<!–

Sys.Application.initialize();

// –>

</script>

First things first: what I do not show here, but what you will see, is that the Ajax script files are loaded.

Then the PageRequestManager is initialized with the name of the scriptmanager, which in our case is “ScriptManager1” or simply the name of the scriptmanager we dropped on out ASP.NET page, and hand it the form object of the page:

Sys.WebForms.PageRequestManager._initialize(‘ScriptManager1’, document.getElementById(‘form1’));

Inside this initialize the PageRequestManager singleton is created and the method _initializeInternal is called. Inside this last method, among some other stuff, eventhandlers (delegates) are created for the forms’ submit and click events and attached to the form. Also, eventhandlers (delegates) are created for the windows’ load and unload events and attached to the window and the windows’ __doPostBack method (which is normally created by ASP.NET as shown above) is, if present, replaced with an AJAX version.

Mind however that all these handlers are added to the existing handlers for the events, except for the submit handler of the form which is first set to null before the handler is added! So if you attached a handler to the forms click event it will still get called.

So, now that we have the PageRequestManager initialized, we can add the update controls:

Sys.WebForms.PageRequestManager.getInstance()._updateControls([‘tUpdatePanel1’], [], [], 90);

There’s not much going on inside this method beside storing the supplied parameters in some internal arrays of the PageRequestManager singleton and keeping an array of booleans with which updatepanel supports children as triggers.

This last thing is rather interesting: if you set the children as triggers property of your updatepanel to false, then the id of the div representing the panel on the client gets prepended with a letter “f”. If the property is left it’s default value of true, the id is prepended with “t”. To store the property on the client side, the first letter is parsed of the id and if it is “t”, then the entry in the array of booleans is set to true.

var childrenAsTriggers = (updatePanelIDs[i].charAt(0) === ‘t’);

this._updatePanelHasChildrenAsTriggers[i] = childrenAsTriggers;

Finally, the page calls the applications initialize() method.

Sys.Application.initialize();

This method calls the _doInitialize eventhandler.

if(!this._initialized && !this._initializing) {

    this._initializing = true;

    window.setTimeout(Function.createDelegate(this, this._doInitialize), 0);

}

This _doInitialize method calls the “init” and “load” eventhandlers if they are defined and also cals the window pageLoad function if it is defined

Push the button: triggering a partial page update

As I explained above, in setting up the page some eventhandlers are attached to the form:

  • To the forms’ submit event the handler “_onFormSubmitHandler” is attached
  • To the forms’ click event the handler “_onFormElementClickHandler” is called

If you press any control inside the updatepanel, then this last handler, the “click” handler, is called.
Inside this handler two things happen:

  1. A method _getPostBackSettings of the PageRequestManager is called. This method walks up the control tree of the page looking for an updatepanel control and then constructs an object with:
    • a boolean indicating if this is an asynchronous request or not.
      It is not if the clicked elements id is in the list _postBackControlClientIDs:

      if (!proposedSettings &&

          this._matchesParentIDInList(element.id,

                                      this._postBackControlClientIDs))

      {

          return this._createPostBackSettings(false, null, null);

      }

      It is if either the element is in the list _asyncPostBackControlClientIDs:

      if (!proposedSettings &&

          Array.contains(this._asyncPostBackControlClientIDs, element.id))

      {

          proposedSettings =

              this._createPostBackSettings(true,

                      this._scriptManagerID + ‘|’ + elementUniqueID,

                      originalElement);

      }

      Or we bubled up to the updatepanel and it has it’s children as triggers:

      var indexOfPanel = Array.indexOf(this._updatePanelClientIDs, element.id);

      if (indexOfPanel !== -1)

      {

          if (this._updatePanelHasChildrenAsTriggers[indexOfPanel])

          {

              return this._createPostBackSettings(true,

                      this._updatePanelIDs[indexOfPanel] + ‘|’ + elementUniqueID,

                      originalElement);

          }

      }

    • a string with the id of the parent updatepanel, or scriptmanager, and the id of the control that was clicked
    • a reference to the clicked control
  2. If the control clicked on the form was an INPUT control of type “submit” or “image”, or a BUTTON control of type “submit”, then a string is made with the name and value of the control, and for an image the click coordinates of the event.

    if (type === ‘submit’)

    {

        this._additionalInput = element.name

                + ‘=’ + encodeURIComponent(element.value);

    }

    else if (type === ‘image’)

    {

        var x = evt.offsetX;

        var y = evt.offsetY;

        this._additionalInput =

                    element.name + ‘.x=’ + x

                    + ‘&’ + element.name + ‘.y=’ + y;

    }

Why only buttons and images?
As you will see in the OnSubmit handler, that method loops over the controls in the form and saves their values,, which contains their state. If you type something in a textbox, or click on a checkbox or radiobutton, then your action changes the state of the control. This means that your action is traceable after it was performed. For clicks on buttons and images this is not the case: after they are clicked nothing to the forms state is changed, so your actions are not traceable. This means that if we want to know what was clicked, we have to maintain that information, which is exactly what happens in the above described OnClick handler of the form: an string is made with the name and value of the clicked control, and when the form is submoitted that string is also send to the server.

Right, so pushing the button triggered the OnClick event handler which saved what button was clicked, but it also triggers the forms OnSubmit handler if the button pushed was of type “submit”.
In the forms OnSubmit handler, following things happen:

It if first checked if this is an asynchronous request by checking the _postBackSettings.async property. Remember we set the _postBackSettings in the OnCick handler as explained above. If it is false, the OnSubmit handler returns, otherwise the method starts with building the body of the asynchronous request. This body is a string of key/value pairs:

  • It starts with the id of the scriptmanager and the value of the _postBackSettings property (remember this contains the name of the updatepanel and control that was clicked and thus initated the request)

    formBody.append(this._scriptManagerID + ‘=’ + this._postBackSettings.panelID + ‘&’);

  • continues is the name and value of the controls in the form, and thus the current state of the form

    formBody.append(name);

    formBody.append(‘=’);

    formBody.append(encodeURIComponent(element.value));

  • and ends with the _additionalInput property (see above)

    formBody.append(this._additionalInput);

Then an asynchronous request object is created which will hold the properties of the request. The ajax type of the object is Sys.Net.WebRequest.
Following properties are set:

  • The url property is set to the forms’ action property
  • Two headers of the request are set:
      A first one marking the request as an Ajax request

      request.get_headers()[‘X-MicrosoftAjax’] = ‘Delta=true’;

    1. A second one prohibiting the server of using it’s cache

      request.get_headers()[‘Cache-Control’] = ‘no-cache’;

  • The timeout property is set to that of the PageRequestManager
  • A delegate is created for the PageRequestManager’s _onFormSubmitCompleted method and added to the request objects completed handler
  • Finally the body of the request is set t the above formed string

Before making the request two handlers are invoked giving you the chance to intervent with the request:

First the initializeRequest event is invoked giving you an opportunity to cancel the event:

var handler = this._get_eventHandlerList().getHandler(“initializeRequest”);

if (handler) {

    var eventArgs = new Sys.WebForms.InitializeRequestEventArgs(request, this._postBackSettings.sourceElement);

    handler(this, eventArgs);

    continueSubmit = !eventArgs.get_cancel();

}

 

if (!continueSubmit) {

    if (evt) {

        evt.preventDefault();

    }

    return;

}

And next the beginRequest event is invoked marking the start of the execution of the request.

handler = this._get_eventHandlerList().getHandler(“beginRequest”);

if (handler) {

    var eventArgs = new Sys.WebForms.BeginRequestEventArgs(request, this._postBackSettings.sourceElement);

    handler(this, eventArgs);

}

Finally the request is effectively made:

request.invoke();

Inside this method, the webrequest calls the Sys.Net.WebRequestManager.executeRequest method, handing it itself as an argument. And inside this method the WebRequestManager creates an executor for the request, first invokes the invokingRequest event and finally executes the request through the executor, unless you cancelled the request through this last event:

var evArgs = new Sys.Net.NetworkRequestEventArgs(webRequest);

var handler = this._get_eventHandlerList().getHandler(“invokingRequest”);

if (handler) {

    handler(this, evArgs);

}

 

if (!evArgs.get_cancel()) {

    executor.executeRequest();

}

The executor is an abstraction around the XMLHttpRequest object of javascript.

Where off …

So now the request is off to the server. In the following article I will explain what happens there.

Resources:

[1] Methods GET and POST in HTML forms – what’s the difference?
[1] XMLHttpRequest
[1] Populating a DropDownList using AJAX and ASP.NET

Updates

6 October 2007: original version

Advertisements

One thought on “A dissection of the ASP.NET AJAX library – Part 3: The PageRequestManager on the client side

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s