if this._changedPostDataConsumers == null this._changedPostDataConsumers = new ArrayList; Next, it iterates through the name/value pairs in the NameValueCollection passed into it as it
Trang 1Asynchronous Par tial Page Rendering: Ser ver Side
Processing
The previous chapter followed the current client-side PageRequestManager instance as it made
an asynchronous page postback or partial-page-rendering request to the server This chapter will move on to the server side to follow the asynchronous page postback request from the time it arrives in ASP.NET to the time the final response text is sent back to the server
Chapter 21 followed the Page object as it went through its life cycle phases to process the first request made to a Web page enabled for partial page rendering Since the first request wasn’t a postback, the Page object skipped the postback-related life cycle phases when it was processing the first request This chapter, on the other hand, follows the current Page object as it goes through its life cycle phases to process an asynchronous page postback request to the same page that the first request downloaded Since an asynchronous page postback is a postback request, the current
Page will go through both postback and non-postback life cycle phases, shown in Listing 21-1 and Figure 21-2
Since the non-postback life cycle phases were discussed thoroughly in Chapter 21 , I’ll discuss only the postback-related life cycle phases in this chapter
RetrievePostData
This is the life cycle phase in which the Page object populates an internal collection of type
NameValueCollection named _requestValueCollection with the posted data, as shown in Listing 23-1 As such, this phase makes sense for postback requests — whether synchronous or asynchronous
Trang 2Listing 23-1: The RetrievePostData Method of the Page Object
private void RetrievePostData()
Depending on the HTTP verb used to make a request, the clients of a page will use one of the following
two approaches to submit data to the server
If the HTTP POST verb is used to make a request, the clients of the page include the data in the body of
the request The data consists of a list of data items separated by the & character, each of which consists
of two parts separated by the equals sign ( = ) The first part of each data item helps the server determine
what type of information the item contains The second part of each data item contains the actual data or
information being submitted As an example, consider the ASP.NET page shown in Listing 23-2 As you
can see, this page contains a TextBox and a DropDownList server control
Every ASP.NET server control inherits a property named UniqueID from the Control base class The
value of this property is automatically set by ASP.NET when the page containing the server control is
accessed This value is a string that contains one or more substrings separated by the dollar sign, of
which the first substring contains the value of the ID property of the control and the subsequent
sub-strings contain the values of the UniqueID properties of those parent controls of the control that
imple-ment the INamingContainer interface In the case of Listing 23-2 , none of the parent controls of the
TextBox and DropDownList server controls (other than the Page itself whose UniqueID property
returns an empty string) implements this interface, which means that ASP.NET sets the values of the
UniqueID properties of these two server controls to the respective values of their ID properties
You may be wondering what the significance of the UniqueID property of a server control is As the
name suggests, this property uniquely identifies the server control among other server controls on the
<form id=”form1” runat=”server”>
<asp:TextBox runat=”server” ID=”TextBox1” />
<asp:DropDownList runat=”server” ID=”DropDownList1”>
<asp:ListItem Text=”Text1” Value=”Value1” />
<asp:ListItem Text=”Text2” Value=”Value2” />
<asp:ListItem Text=”Text3” Value=”Value3” />
</asp:DropDownList>
Trang 3<asp:Button runat=”server” Text=”Submit” />
<input name=”TextBox1” type=”text” id=”TextBox1” />
<select name=”DropDownList1” id=”DropDownList1”>
<option value=”Value1”>Text1</option>
<option value=”Value2”>Text2</option>
<option value=”Value3”>Text3</option>
</select>
Listing 23-3: The HTML Markup Text Sent to the Client
<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
<input name=”TextBox1” type=”text” id=”TextBox1” />
<select name=”DropDownList1” id=”DropDownList1”>
❑ The value of the name HTML attribute of the select HTML element — that is, the string
“DropDownList1” Recall that this value is that of the UniqueID property of the DropDownList server control
Trang 4❑ The string that the end user has entered into the text field — that is, the string MyText
❑ The value of the value HTML attribute of the selected option of the select HTML element —
that is, the string “Value2”
Therefore, the data that the browser needs to send to the server consists of two data items The first
con-tains the string that the end user has entered into the text field, and the second concon-tains the value of the
value HTML attribute of the selected option of the select HTML element:
TextBox1=MyText&DropDownList1=Value2
As you can see, each data item consists of two parts The first part is the value of the UniqueID property
of the server control and the second part is the value associated with the server control The UniqueID
property values allow ASP.NET to determine which data item is associated with which server control
So far, I’ve covered the case in which the client uses the HTTP POST verb to make its request to the
server The second scenario is when the client uses the HTTP GET verb This scenario often involves
e-commerce Web applications For example, consider a page that displays the list of product names to
the end users When a user selects a product to see more details about it, the primary key values of the
product and its distributor are passed as a query string to the server:
http://www.mysite.com/Product.aspx?ProductID=2&DistributorID=3
As you can see, the query string consists of a list of data items separated by the ampersand character,
each of which consists of two parts separated by the equals sign
ASP.NET represents each request with an instance of a class named HttpReques t, which exposes two
collection properties of type NameValueCollection named Form and QueryString ASP.NET
auto-matically populates the Form collection with the posted data if the request was made using the HTTP
POST verb; otherwise it populates the QueryString collection with the posted data
Keep in mind that we’re following the current Page through its life cycle phases to process the
asynchro-nous page postback method that the current client-side PageRequestManager instance has made to the
server As we just discussed, the clients of a page have two options when it comes to submitting data to
the server Which option did the current client-side PageRequestManager instance use to submit its
data to the server? The answer lies in Listing 22-22 Recall that this code listing presents the internal
implementation of the _onFormSubmit method of the current client-side PageRequestManager
instance As we discussed in the previous chapter, this method is automatically invoked when a page
postback occurs, allowing the current client-side PageRequestManager instance to intercept
the page postback before the page is actually posted back to the server The current client-side
PageRequestManager instance then determines whether the page must be posted back asynchronously
If so, it takes over the form submission, bypassing the browser’s default synchronous form submission
As Listing 22-22 shows, the current client-side PageRequestManager instance iterates through all the
input form elements on the current page, generates for each input form element one string that consists
of two substrings separated by the equals sign (the first substring containing the value of the name
HTML attribute of the form element and the second containing the value of the form element), and
finally packs all these strings into a single string using the & character as the separator Recall from
Listing 22-22 that the current client-side PageRequestManager instance adds this string to the body of
Trang 5the request being made to the server As you can see, the current client-side PageRequestManager instance submits its data to the server via the body of an HTTP POST request
Now back to the implementation of the RetrievePostData method of the Page object, as shown in Listing 23-1 Recall that the Page object calls this method when it enters the Retrieve Post Data life cycle phase As this code listing shows, this method simply stores the content of the Form or QueryString collection of the HttpRequest object that represents the current request in an internal collection named
_requestValueCollection This collection contains one name/value pair for each posted data item Recall that each posted data item consists of two parts separated by the equals sign The name part of each name/value pair in this collection contains the first part of the data item and the value part contains the second part of the data item This means that the first part of the data item can be used as an index into the collection to access the second part of the data item For example, in the case of the examples dis-cussed earlier, the following items are true:
❑ The UniqueID property value of the TextBox server control can be used as an index into the
_requestValueCollection to access the text that the end user has entered into the text field:
string text1 = this._requestValueCollection[“TextBox1”];
❑ The UniqueID property value of the DropDownList server control can be used as an index into the _requestValueCollection to access the value of the value HTML attribute of the selected option of the select HTML element associated with the server control:
string text1 = this._requestValueCollection[“DropDownList1”];
❑ The string “ProductID” can be used as an index into the _requestValueCollection to access the primary key value of the product:
string text1 = this._requestValueCollection[“ProductID”];
❑ The string “DistributorID” can be used as an index into the _requestValueCollection to access the primary key value of the distributor:
string text1 = this._requestValueCollection[“DistributorID”];
Since the current Page is processing the asynchronous request shown in Listing 22-22 , the
_requestValueCollection of the current Page contains all the name/value pairs that Listing 22-22 stuffed into the body of the request
LoadScrollPosition
This is the life cycle phase in which the Page object retrieves the scroll x and y positions from the
_requestValueCollection and assigns them to the _scrollPositionX and _scrollPositionY fields, respectively, as shown in Listing 23-4 As you’ll see later, the Page object uses these two fields to set the scroll position in the response text before submitting the response back to the client This life cycle phase takes effect only if the MaintainScrollPositionOnPostBack property of the Page object has been set to true As the name suggests, this property instructs the Page to maintain the scroll position
on page postbacks — be they synchronous or asynchronous
Trang 6Listing 23-4: The LoadScrollPosition Method of the Page Object
Private void LoadScrollPosition()
{
if (this._requestValueCollection != null)
{
string text1 = this._requestValueCollection[“ SCROLLPOSITIONX”];
if ((text1 != null) && !int.TryParse(text1, out this._scrollPositionX))
this._scrollPositionX = 0;
string text2 = this._requestValueCollection[“ SCROLLPOSITIONY”];
if ((text2 != null) && !int.TryParse(text2, out this._scrollPositionY))
PageRequestManager instance, and since the _requestValueCollection contains only the name/
value pairs that the current client-side PageRequestManager instance stuffed into the body of the
request, you may be wondering where the scroll x and y values come from The answer lies in
Listing 22-22 itself, which is partially repeated in the following code listing
Recall that this code listing contains the code for the _onFormSubmit method of the current client-side
PageRequestManager instance As discussed earlier, this method is automatically invoked when a
post-back occurs As the highlighted portions of the following code listing show, the current client-side
PageRequestManager instance iterates through all the input form elements on the page, including the
hidden fields named SCROLLPOSITIONX and SCROLLPOSITIONY , and forms for each input form
element one string that consists of two substrings, the first containing the value of the name HTML
attribute of the input form element (in this case, these values are SCROLLPOSITIONX and
SCROLLPOSITIONY ) and the second containing the value of the value HTML attribute of the input
form element (in this case, these values are the scroll x and y positions)
As the boldface portion of the following code listing shows, the current client-side PageRequestManager
instance stores the current scroll x and y positions in an internal field named _scrollPostion before it
submits the request to the server As you’ll see later, when the server response arrives, the current
client-side PageRequestManager instance retrieves the new scroll x and y positions from the response
and compares them with the old values stored in the _scrollPosition field to determine whether the
scroll x and y positions have indeed changed
Trang 7formBody.append(encodeURIComponent(element.value));
formBody.append(‘&’);
} } } this._scrollPosition = this._getScrollPosition();
}
InitRecursive
I covered the non-postback-related parts of the InitRecursive life cycle phase of the Page object
in Chapter 21 ; therefore I’ll just cover the postback-related parts of this phase in this section Recall from Chapter 21 that the OnInit method of the current ScriptManager instance is automatically invoked when the current Page enters its Init phase Listing 23-5 presents the ScriptManager class’s internal implementation of the OnInit method, which it inherits from the Control base class I discussed all the parts of Listing 23-6 in Chapter 21 except for the highlighted portion, which is applicable only to post-back requests As you can see, this portion calls the IsAsyncPostBackRequest static method on the server-side PageRequestManager , passing in the NameValueCollection that contains the names and values of the request headers The main responsibility of this method is to determine whether the current request is an asynchronous page postback
Listing 23-5: The OnInit Method of the ScriptManager Class
protected override void OnInit(EventArgs e){
base.OnInit(e);
if (ScriptManager.GetCurrent(this.Page) != null) throw new InvalidOperationException(“OnlyOneScriptManager”);
this.IPage.Items[typeof(ScriptManager)] = this;
this.IPage.PreRenderComplete += new EventHandler(this.OnPagePreRenderComplete);
if (this.IPage.IsPostBack) this._isInAsyncPostBack = PageRequestManager.IsAsyncPostBackRequest(this.IPage.Request.Headers); this.PageRequestManager.OnInit();
string[] textArray1 = headers.GetValues(“X-MicrosoftAjax”);
Trang 8Then it iterates through these values searching for a value that contains the string “Delta=true” If it
finds a value that contains this string, it returns true to signal its caller that the current request is an
asynchronous page postback
Listing 22-22 contains the client-side code that made the current asynchronous page postback The
fol-lowing code listing repeats a portion of Listing 22-22 As the highlighted portion of the folfol-lowing code
shows, the current client-side PageRequestManager instance added the “Delta=true” header value to
a custom request header named “X-MicrosoftAjax” to signal the server-side PageRequestManager
instance that the current request is an asynchronous page postback
string[] textArray2 = textArray1[num1].Split(new char[] { ‘,’ });
for (int num2 = 0; num2 < textArray2.Length; num2++)
The OnInit Method of PageRequestManager
Recall from Chapter 21 that the OnInit method of the current server-side PageRequestManager
instance is automatically invoked when the Page object enters its Init life cycle phase Listing 23-7
pres-ents the internal implementation of the OnInit method of the server-side PageRequestManager I
cov-ered all the parts of this method in Chapter 21 , except for the highlighted portion, because this portion is
run only when the current request is an asynchronous page postback As you can see, this portion simply
registers the OnPageError method of the current server-side PageRequestManager instance as an event
handler for the Error event of the current Page object
Trang 9Listing 23-7: The OnInit Method of the PageRequestManager Class
internal void OnInit(){
if (this._owner.IsInAsyncPostBack) this._owner.IPage.Error += new EventHandler(this.OnPageError);
}
Load Post Data
Currently we’re at the Load Post Data life cycle phase, in which the ProcessRequest method (see Listing 21-1 ) invokes the ProcessPostData method of the current Page , passing in two parameters The first parameter is the _requestValueCollection field of the current Page Recall that this field is a col-lection of type NameValueCollection that contains one name/value pair for each posted data item, the name part containing the UniqueID property value of a server control and the value part containing the value associated with that server control For example, the name part of the name/value pair associ-ated with a TextBox server control contains the value of the UniqueID property of the TextBox control, and the value part contains the text that the end user has entered into the text field
As Listing 21-1 shows, the ProcessRequest method of the current Page passes true as the second ment of the ProcessPostData method to instruct this method that the Page is currently in a pre-Load life cycle phase As you’ll see later, the same ProcessPostData method will also be called after the Load life cycle phase The second Boolean argument allows this method to distinguish between these two calls
Listing 23-8 presents the internal implementation of the ProcessPostData method of the Page
Listing 23-8: The ProcessPostData Method of the Page Object
private void ProcessPostData(NameValueCollection postData, bool fBeforeLoad){
if (this._changedPostDataConsumers == null) this._changedPostDataConsumers = new ArrayList();
foreach (string text1 in postData) {
if (!Page.IsSystemPostField(text1)) {
Control control1 = this.FindControl(text1);
if (control1 == null) {
if (fBeforeLoad) {
if (this._leftoverPostData == null) this._leftoverPostData = new NameValueCollection();
this._leftoverPostData.Add(text1, null);
} } else { IPostBackDataHandler handler1 = control1 as IPostBackDataHandler;
Trang 10This method first instantiates an ArrayList field named _changePostDataConsumers , if it hasn’t
already been instantiated You’ll see the significance of this field later
if (this._changedPostDataConsumers == null)
this._changedPostDataConsumers = new ArrayList();
Next, it iterates through the name/value pairs in the NameValueCollection passed into it as its first
argument, and takes the following actions for each enumerated name/value pair if the name part of the
pair does not contain the name attribute value of one of the standard hidden fields such as VIEWSTATE
(the name/value pairs associated with standard hidden fields will be processed later):
❑ The ProcessPostData method calls the FindControl method on the current Page object to
return a reference to the server control whose UniqueID property value is given by the name
part of the enumerated name/value pair:
Trang 11Control control1 = this.FindControl(text1);
❑ If the current Page does not contain a server control with this UniqueID property value, this indicates that the server control has been dynamically added to the Page during the Page ’s Load life cycle phase For example, the page developers could add server controls to the current
Page within the Page_Load method, which is invoked when the Page enters its Load life cycle phase Since we’re currently at the Load Post Data life cycle phase, which occurs before the Load life cycle phase (see Figure 21-2 ), the ProcessPostData method does not process the enumer-ated name/value pair and instead stores the value of the name part of the pair in a collection named _leftOverPostData so that this name/value pair can be processed after the Load life cycle phase — that is, after the associated server control is added to the current Page :
if (this._leftoverPostData == null) this._leftoverPostData = new NameValueCollection();
this._leftoverPostData.Add(text1, null);
❑ If the current Page contains a server control whose UniqueID property value is given by the name part of the enumerated name/value pair, the ProcessPostData method takes the follow-ing steps to process the enumerated name/value pair First, it checks whether the server control implements the IPostBackDataEventHandler interface:
❑ If not, it checks whether the server control implements the IPostBackEventHandler interface If so, it calls the RegisterRequiresRaiseEvent method of the current Page , passing in the server control This method stores this server control in an internal field for future reference As you’ll see later, when the current Page enters its RaisePostBackEvent life cycle phase, it will automatically call the RaisePostBackEvent method of this server control In general, controls that implement the IPostBackEventHandler interface raise postback events
IPostBackDataHandler handler1 = control1 as IPostBackDataHandler;
if (handler1 == null) {
if (control1 as IPostBackEventHandler != null) this.RegisterRequiresRaiseEvent((IPostBackEventHandler)control1);
}
❑ If so, it calls the LoadPostData method on the associated server control, passing in two parameters The first parameter is the name part of the enumerated name/value pair, and the second parameter is the _requestValueCollection field Recall that this field con-tains all the name/value pairs (or data items) that the client has posted back to the server
❑ It is the responsibility of the LoadPostData method of the server control to use its first parameter as an index into the second parameter to retrieve the value of the value part of the associated name/value pair (or data item) For example, the LoadPostData method
of an ASP.NET TextBox server control uses its UniqueID property value as an index into the _requestValueCollection to retrieve the text that the end user has entered into the text field This method then compares this text with the value of the Text property
of the TextBox control If these two values are different, the end user has changed the original value of the text field As a result, the LoadPostData method returns true to signal the ProcessPostData method that its value has changed and that therefore its
RaisePostDataChangedEvent method must be invoked when the current Page enters its
RaisePostDataChangedEvent life cycle phase (see Figure 21-2 )
Trang 12❑ Note that if the LoadPostData method returns true , the ProcessPostData method adds
the server control to an internal collection named _changedPostDataConsumers As you’ll see later, when the current Page object enters its RaisePostDataChangedEvent life cycle phase, it will iterate through the server controls in the _changedPostDataConsumers collection and invoke their RaisePostDataChangedEvent methods
if (handler1.LoadPostData(text1, this._requestValueCollection))
this._changedPostDataConsumers.Add(control1);
❑ Finally, the ProcessPostData method removes the current UniqueID property value
from the _controlsRequiringPostBack collection This collection maintains the
UniqueID property values of those server controls whose RaisePostBackEvent methods must be called when the current Page enters the RaisePostBackEvent life cycle phase
this._controlsRequiringPostBack.Remove(text1);
Next, the ProcessPostData method iterates through the items in the _controlsRequiringPostBack
collection Recall that this collection contains the UniqueID property values of those server
controls whose RaisePostBackEvent method must be invoked when the current Page enters the
RaisePostBackEvent life cycle phase The ProcessPostData method takes the following actions for
each enumerated UniqueID property value in this collection:
❑ First, it calls the FindControl method on the current Page to return a reference to the server
control with the enumerated UniqueID property value:
Control control2 = this.FindControl(text2);
❑ If the current Page object contains a server control with the enumerated UniqueID property value,
the ProcessPostData method calls the LoadPostData method on the server control, and if this
method returns true , it adds the server control to the _changedPostDataConsumers collection:
❑ If the current page does not contain a server control with the enumerated UniqueID property
value, the ProcessPostData method adds the server control to a local array, which is finally
added to the _controlsRequiringPostBack collection:
Trang 13UpdatePanel
As these discussions show, the Load Post Data life cycle phase of the current Page object is applicable only to those server controls that implement the IPostBackDataHandler interface Since the current implementation of the UpdatePanel server control does not implement this interface, none of the
UpdatePanel server controls on the current Page will participate in the current Page ’s Load Post Data life cycle phase However, you can write a custom UpdatePanel server control that inherits the
UpdatePanel server control and implements the IPostBackDataHandler interface If you do so, the current Page object will automatically call the LoadPostData method of your custom UpdatePanel server control, and if your implementation of this method returns true , the current Page object will also automatically call the RaisePostDataChangedEvent method of your custom control
ScriptManager
As the boldface portion of Listing 23-9 shows, the ScriptManager implements the
IPostBackDataHandler interface As I already mentioned, this interface exposes two methods named LoadPostData and RaisePostDataChangedEvent
Listing 23-9: The Declaration of the ScriptManager Class
[ParseChildren(true), DefaultProperty(“Scripts”), NonVisualControl,PersistChildren(false)]
public class ScriptManager : Control, IPostBackDataHandler, IControl, IClientUrlResolver, IScriptManagerInternal{
protected virtual bool LoadPostData(string postDataKey,
of the current ScriptManager instance passes the same two parameters that were passed into it into the
LoadPostData method of the current server-side PageRequestManager instance
Trang 14The LoadPostData Method of PageRequestManager
Listing 23-10 presents the internal implementation of the LoadPostData method of the server-side
PageRequestManager Recall that when the LoadPostData method of the current ScriptManager
server control calls the LoadPostData method of the current server-side PageRequestManager
instance, it passes two parameters into it The first parameter is a string that contains the value of the
UniqueID property of the current ScriptManager server control The second parameter is the
NameValueCollection that contains all the name/value pairs (each pair represents a posted data item)
that the current client-side PageRequestManager instance has posted back to the server
Recall that Listing 22-22 presents the internal implementation of the _onFormSubmit method of the
current client-side PageRequestManager instance This method is where the current client-side
PageRequestManager instance posts all its name/value pairs back to the server asynchronously The
following code listing repeats a portion of Listing 22-22 :
As the highlighted portion of this code listing shows, the current client-side PageRequestManager
instance posts a name/value pair whose name part contains the UniqueID property value of the current
ScriptManager server control, and whose value part contains the value of the panelID property of the
postback settings JavaScript object
Recall that Listing 22-22 shows where the value of the panelID property of the postback settings JavaScript
object is set This code listing contains the implementation of the _getPostBackSettings method of the
current client-side PageRequestManager instance As discussed in Chapter 22 , this method uses the
fol-lowing logic to set the value of the panelID property of the postback settings JavaScript object:
❑ If the server control that caused the current page postback resides in an UpdatePanel server
control whose ChildrenAsTriggers property is set to true , the _getPostBackSettings
method sets the value of the panelID property to a string that contains two substrings
sepa-rated by the pipe character ( | ), of which the first substring contains the UniqueID property
value of the UpdatePanel server control, and the second the UniqueID property value of the
server control that caused the current page postback As you’ll see shortly, the presence of the
UniqueID property value of this UpdatePanel server control signals the current server-side
PageRequestManager instance that this UpdatePanel server control must be updated
When a page postback is caused by a server control that resides in an UpdatePanel server control
whose ChildrenAsTriggers property is set to true , it automatically triggers the update of its
par-ent UpdatePanel server control
❑ If the _asyncPostBackControlIDs collection of the current client-side PageRequestManager
instance contains the UniqueID property value of the server control that caused the current
Trang 15page postback, or one of its ancestor server controls, but the server control itself does not reside
in an UpdatePanel server control whose ChildrenAsTrigger property is set to true ,
_getPostBackSettings sets the value of the panelID property to a string that contains two substrings separated by the | character: the first substring contains the UniqueID property value of the ScriptManager server control, and the second substring contains the UniqueID property value of the server control that caused the current page postback
Keep this logic in mind as we’re walking through the implementation of the LoadPostData method of the server-side PageReqeustManager As you can see from Listing 23-10 , this method first uses its first parameter, which is nothing but the UniqueID property value of the current ScriptManager server control, as an index into the NameValueCollection to return the associated posted string value data
string text1 = postCollection[postDataKey];
This string value consists of up to two substrings separated by the | character As we discussed earlier, the second substring is the UniqueID property value of the server control that caused the current page postback The LoadPostData method assigns this substring to the _asyncPostBackSourceElementID field for future reference
As we also discussed earlier, the first substring contains the value of the UniqueID property of either the
ScriptManager server control or the UpdatePanel control whose content must be updated If the string does not contain the value of the UniqueID property of the ScriptManager server control — that
sub-is, if it contains the value of the UniqueID property of a specific UpdatePanel server control — the
LoadPostData method assigns this substring to the _updatePanelRequestUpdate field:
while (enumerator1.MoveNext()) {
enumerator1.Current.Initialize();
} }
Finally, the LoadPostData method sets an internal flag named _panelsInitialized to signal that all
UpdatePanel controls on the page have been initialized:
this._panelsInitialized = true;
Trang 16Listing 23-10: The LoadPostData Method of the PageRequestManager
internal void LoadPostData(string postDataKey, NameValueCollection postCollection)
Figure 23-1 contains all the method calls that occur when the current Page object enters its Load Post
Data life cycle phase
LoadPostData
Page ScriptManager PageRequestManage UpdatePanel
LoadPostData (postdataKey, postCollection)
Initialize ( ) LoadPostData (postdataKey, postCollection)
Figure 23-1
The Raise Post Data Changed Event
When the Page object enters its Raise Post Data Changed Event phase (see Figure 21-2 ), it calls the
RaisePostDataChangedEvent methods of those server controls that meet the following two requirements:
❑ They implement the IPostBackDataHandler interface
❑ Their implementation of the LoadPostData method returns true
Trang 17The ScriptManager server control meets the first requirement However, it does not meet the second requirement because its LoadPostData method returns false, as shown in Listing 23-9 Therefore, the
RaisePostDataChangedEvent method of the ScriptManager server control is never invoked As the following code listing shows, this method does not do anything anyway:
protected virtual void RaisePostDataChangedEvent(){
}
That said, you can implement a custom ScriptManager that extends the functionality of the
ScriptManager server control, where its implementation of the LoadPostData method returns true,
to have the Page object invoke its RaisePostDataChangedEvent Your custom ScriptManager control’s implementation of this method can then take the appropriate actions in response
Listing 23-11: The OnPreRender Method of the ScriptManager
protected override void OnPreRender(EventArgs e){
base.OnPreRender(e);
if (this.IsInAsyncPostBack) this.PageRequestManager.OnPreRender();
}
The OnPreRender Method of PageRequestManager
Listing 23-12 presents the implementation of the OnPreRender method of the server-side
PageRequestManager The Page object, like any other server control, inherits a method named
SetRenderMethodDelegate from the Control base class This method registers a delegate of type RenderMethod that represents another method In the case of Listing 23-12 , the
SetRenderMethodDelegate method registers a delegate that represents the RenderPageCallback method of the server-side PageRequestManager
As you’ll see later in this chapter, when the server control on which the SetRenderMethodDelegate method was invoked enters its rendering phase, in which its RenderChildren method is invoked, the
RenderChildren method invokes the RenderMethod delegate, and consequently the method that the delegate represents, bypassing the normal rendering logic that renders the child controls of the server control
Trang 18In the case of Listing 23-12 , when the Page server control enters its rendering phase in which its
RenderChildren method is called, the RenderChildren method will invoke the delegate that
repre-sents the RenderPageCallback method of the PageRequestManager , bypassing the normal rendering
logic that renders the server controls in the Controls collection of the Page This allows the server-side
PageRequestManager to take complete control of the rendering of the server controls in the Controls
collection of the Page object when the current request is an asynchronous page postback
Listing 23-12: The OnPreRender Method of the PageRequestManager
internal void OnPreRender()
In this section, we’ll see what happens when the Page enters its rendering life cycle phase, in which the
Render method of the Page is invoked The Page inherits the Render method from the Control base
class Listing 23-13 presents the implementation of the Render method of the Control base class
Listing 23-13: The Render Method of the Control Base Class
protected internal override void Render(HtmlTextWriter writer)
{
this.RenderChildren(writer);
}
As you can see, the Render method of the Control base class simply calls the RenderChildren method
shown in Listing 23-14 Recall from Listing 23-12 that the OnPreRender method of the server-side
PageRequestManager created a RenderMethod delegate that represents the RenderPageCallback
method of the server-side PageRequestManager and invoked the SetRenderMethodDelegate method
on the Page object , passing in this RenderMethod delegate
Every server control, including the Page object, inherits the RenderChildren method shown in
Listing 23-14 from the Control base class As you can see, the RenderChildren method of a server
control first calls the GetRenderMethod to return a reference to the RenderMethod delegate registered
with the server control, if any In the case of the Page server control, since the current request is an
asynchronous page postback, the GetRenderMethod returns a reference to the RenderMethod delegate
that represents the PageRenderCallback method of the PageRequestManager
As Listing 23-14 shows, the RenderChildren method of a server control bypasses the normal rendering
logic of the server control’s child controls if a RenderMethod delegate has been registered with the
server control As the boldface portion of Listing 23-14 shows, the normal rendering logic of the server
control’s child controls simply iterates through the child controls in the server control’s Controls
collection and invokes the RenderControl method on each enumerated child control
In the case of the Page server control, if the current request is an asynchronous page postback, the
RenderChildren method invokes the registered RenderMethod delegate, which in turn invokes the
Trang 19PageRenderCallback method of the server-side PageRequestManager The end result of all this is that the server-side PageRequestManager takes complete control over what gets rendered when the current request is an asynchronous page postback
Listing 23-14: The RenderChildren Method of the Control Base Class
protected internal virtual void RenderChildren(HtmlTextWriter writer){
RenderMethod renderMethod = this.GetRenderMethod();
if (renderMethod != null) {
writer.BeginRender();
renderMethod(writer, this);
writer.EndRender();
} else if (this.Controls != null) {
foreach (Control control in this.Controls)
{
control.RenderControl(writer);
}
}}
The Encode Method of PageRequestManager
Since the implementation of the RenderPageCallback method makes use of another method of
PageRequestManager named EncodeString , I’ll discuss the implementation of this method first
Listing 23-15 presents the internal implementation of the EncodeString method of the server-side
PageRequestManager This method takes four parameters as follows, encodes the values of its second through fourth parameters into a string, and writes this encoded string into the TextWriter object refer-enced by its first parameter
❑ writer : This parameter references the TextWriter instance in which the encoded string is stored This is normally an HtmlTextWriter instance that wraps the response output stream
❑ type : This parameter is a string that specifies the type of information that the encoded string contains As you’ll see later, the current client-side PageRequestManager instance will use this string to determine what type of information it is dealing with For example, if the encoded string contains the value of a hidden field, the server-side PageRequestManager instance uses the string “hiddenField” as the type to tell the current client-side PageRequestManager in-stance that the encoded string contains the name and value of a hidden field
❑ id : As you’ll see later, if the encoded string contains the value associated with a server control, this optional parameter contains the value of the ClientID property of the server control
❑ content : This parameter contains the actual value being encoded
As Listing 23-15 shows, the EncodeString method generates a string that contains four substrings rated by the | character, where the second, third, and fourth substrings contain the second and third parameters, and the encoded form of the fourth parameter, of the EncodeString method
Trang 20Listing 23-15: The EncodeString Method of PageRequestManager
internal static void EncodeString(TextWriter writer, string type, string id,
char[] chArray1 = content.ToCharArray();
for (int num4 = 0; num4 < chArray1.Length; num4++)
The RenderPageCallback Method of PageRequestManager
Now back to the implementation of the RenderPageCallback method of the server-side
PageRequestManager , as shown in Listing 23-16 As you can see, this method takes two arguments
The first references the HtmlTextWriter instance that wraps the response output stream, which means
that anything that this method writes into this HtmlTextWriter instance will be automatically written
into the response output stream The second argument references the Page object The main
responsibility of this method is to render the Page object and its contents to the specified
HtmlTextWriter instance and consequently to the response output stream
As you can see, the RenderPageCallback method first calls the ProcessUpdatePanels method on
the current server-side PageRequestManager instance As you’ll see later in this chapter, the main
Trang 21responsibility of this method is to determine which UpdatePanel server controls on the current page must be updated
this.ProcessUpdatePanels();
The RenderPageCallback method then sets the ContentType property of the ASP.NET Response object to text/plain to inform the current client-side PageRequestManager instance that the body of the response contains plain text:
IHttpResponse response1 = this._owner.IPage.Response;
response1.ContentType = “text/plain”;
Next, it calls the SetNoServerCaching on the ASP.NET Cache object to turn off server-side output caching for the current response, because the current request is an asynchronous page postback, which only updates specific portions of the page — that is, the portions encapsulated in the UpdatePanel server controls If the output caching were allowed, the next synchronous request for the page would be served from the cache and consequently the client would get HTML that contains only portions of the original page:
response1.Cache.SetNoServerCaching();
Next, the RenderPageCallback method creates a delegate of type RenderMethod that represents the
RenderFormCallback method of the server-side PageRequestManager :
RenderMethod renderMethod = new RenderMethod(this.RenderFormCallback);
Then it calls the SetRenderMethodDelegate method on the HtmlForm server control that represents the <form runat=”server”> HTML element of the current page to register the RenderMethod dele-gate with the HtmlForm server control As we discussed earlier, the HtmlForm server control, like any other, inherits the SetRenderMethodDelegate method from the Control base class When the
HtmlForm server control enters its rendering phase, in which its RenderChildren method is invoked, the RenderChildren method will call the delegate and consequently the RenderFormCallback method that the delegate represents, bypassing the normal rendering logic of the HtmlForm server control’s child controls Recall that this normal logic simply iterates through all the visible child controls of the
UpdatePanel server controls must be rendered; the rest of the page must be left alone
IHtmlForm form1 = this._owner.IPage.Form;
this._updatePanelWriter = writer;
Trang 22Then the method instantiates an instance of an internal StringWriter subclass named
ParserStringWriter A string writer is a stream that contains an internal string in which it
accumulates the information written into the stream
PageRequestManager.ParserStringWriter writer1 =
new PageRequestManager.ParserStringWriter();
Next, the RenderPageCallback method instantiates an instance of an internal HtmlTextWriter
sub-class named ParserHtmlTextWriter that wraps the ParserStringWriter instance Note the
differ-ence between the ParserHtmlTextWriter instance and the HtmlTextWriter instance stored in the
_updatePanelWriter field While the former wraps the ParserStringWriter , the latter wraps the
response output stream This means that anything written into the former will be automatically written
into the ParserStringWriter , which is nothing but a string writer, and anything written into the latter
will be automatically written into the response output stream, which is sent back to the client In other
words, what’s written into the ParserHtmlTextWriter instance remains in the server memory for the
duration of the current request, while what’s written into the HtmlTextWriter instance stored in the
_updatePanelWriter field is sent to the client
PageRequestManager.ParserHtmlTextWriter writer2 =
new PageRequestManager.ParserHtmlTextWriter(writer1);
writer1.ParseWrites = true;
Then the RenderPageCallback method calls the RenderControl method on the HtmlForm server
con-trol to render the concon-trol and its child concon-trols into the ParserHtmlTextWriter instance Since the
RenderControl method renders the HtmlForm server control and its child controls into an in-memory
stream, as opposed to the response output stream, what gets rendered remains in memory, allowing the
RenderPageCallback method to decide which part of this rendered HTML markup must be sent to
the client
form1.RenderControl(writer2);
writer1.ParseWrites = false;
The HtmlForm server control may contain hidden fields When the RenderControl method of the
HtmlForm server control renders these hidden fields into the ParserHtmlTextWriter instance, this
instance stores these hidden fields in a collection named HiddenFields This collection contains one
KeyValuePair object for each hidden field: the name part of the pair contains the name of the hidden
field and the value part contains the value
The RenderPageCallback method iterates through the KeyValuePair objects in this collection and
takes the following steps for each enumerated pair First, it checks whether the enumerated pair
represents a standard hidden field If so, it calls the EncodeString static method on the
PageRequestManager class
As we discussed earlier, the EncodeString method first creates an encoded string that consists of three
main substrings separated by the | character — “hiddentField”|pair1.Key.ToString()|pair1
.Value.ToString() — in which the Key and Value properties of the KeyValuePair object contain the
name and value of the hidden field, respectively Next, the EncodeString method writes this encoded
string into the HtmlTextWriter object that wraps the response output stream
Trang 23foreach (KeyValuePair<string, string> pair1 in writer1.HiddenFields) {
if (PageRequestManager.IsBuiltInHiddenField(pair1.Key)) PageRequestManager.EncodeString(writer, “hiddenField”, pair1.Key, pair1.Value);
PageRequestManager.EncodeString(writer, “asyncPostBackControlIDs”, string.Empty, this.GetAsyncPostBackControlIDs(false));
❑ “postBackControlIDs”|””|this.GetPostBackControlIDs(false) : The first part of this encoded string tells the current client-side PageRequestMananger instance that this string con-tains the list of the UniqueID property values of all server controls on the current page that cause synchronous page postbacks:
PageRequestManager.EncodeString(writer, “postBackControlIDs”, string.Empty, this.GetPostBackControlIDs(false));
❑ “updatePanelIDs”|””|this.GetAllUpdatePanelIDs() : The first part of this encoded string tells the current client-side PageRequestMananger instance that this string contains the list of the UniqueID property values of all UpdatePanel server controls on the current page:
PageRequestManager.EncodeString(writer, “updatePanelIDs”, string.Empty, this.GetAllUpdatePanelIDs());
❑ “childUpdatePanelIDs”|””|this.GetChildUpdatePanelIDs() : The first part of this encoded string tells the current client-side PageRequestMananger instance that this string contains the list of the UniqueID property values of all UpdatePanel server controls on the current page that reside inside another UpdatePanel server control:
PageRequestManager.EncodeString(writer, “childUpdatePanelIDs”, string.Empty, this.GetChildUpdatePanelIDs());
❑ “panelsToRefreshIDs”|””|this.GetRefreshingUpdatePanelIDs() : The first part of this encoded string tells the current client-side PageRequestMananger instance that this string con-tains the list of the UniqueID property values of all UpdatePanel server controls on the current page that need refreshing:
PageRequestManager.EncodeString(writer, “panelsToRefreshIDs”, string.Empty, this.GetRefreshingUpdatePanelIDs());
Trang 24❑ “asyncPostBackTimeout”|””|this._owner.AsyncPostBackTimeout.ToString() : The
first part of this encoded string tells the current client-side PageRequestMananger instance that
this string contains the value of the asynchronous page postback timeout:
PageRequestManager.EncodeString(writer, “asyncPostBackTimeout”, string.Empty,
this._owner.AsyncPostBackTimeout.ToString());
❑ “formAction”|””|writer2.FormAction : The first part of this encoded string tells the
current client-side PageRequestMananger instance that this string contains the value of the
form action:
if (writer2.FormAction != null)
PageRequestManager.EncodeString(writer, “formAction”, string.Empty,
writer2.FormAction);
❑ “pageTitle”|””|this._owner.IPage.Title : The first part of this encoded string tells the
current client-side PageRequestMananger instance that this string contains the value of
the page title:
Next, the RenderPageCallback method calls the RenderDataItems method on the current server-side
PageRequestManager instance to render the data items into the server output stream:
Finally, the RenderPageCallback method calls the ProcessFocus method on the current server-side
PageRequestManager instance to give the mouse focus to the appropriate server control
this.ProcessFocus(writer);
Listing 23-16: The RenderPageCallback Method of the PageRequestManager
private void RenderPageCallback(HtmlTextWriter writer, Control pageControl)
Trang 25this._updatePanelWriter = writer;
PageRequestManager.ParserStringWriter writer1 = new PageRequestManager.ParserStringWriter(); PageRequestManager.ParserHtmlTextWriter writer2 =
new PageRequestManager.ParserHtmlTextWriter(writer1); writer1.ParseWrites = true;
} PageRequestManager.EncodeString(writer, “asyncPostBackControlIDs”, string.Empty, this.GetAsyncPostBackControlIDs(false));
PageRequestManager.EncodeString(writer, “postBackControlIDs”, string.Empty, this.GetPostBackControlIDs(false));
PageRequestManager.EncodeString(writer, “updatePanelIDs”, string.Empty, this.GetAllUpdatePanelIDs());
PageRequestManager.EncodeString(writer, “childUpdatePanelIDs”, string.Empty, this.GetChildUpdatePanelIDs());
PageRequestManager.EncodeString(writer, “panelsToRefreshIDs”, string.Empty, this.GetRefreshingUpdatePanelIDs());
PageRequestManager.EncodeString(writer, “asyncPostBackTimeout”, string.Empty, this._owner.AsyncPostBackTimeout.ToString());
if (writer2.FormAction != null) PageRequestManager.EncodeString(writer, “formAction”, string.Empty, writer2.FormAction);
if (this._owner.IPage.Header != null) {
string text1 = this._owner.IPage.Title;
if (!string.IsNullOrEmpty(text1)) PageRequestManager.EncodeString(writer, “pageTitle”, string.Empty, text1);
} this.RenderDataItems(writer);
this.ProcessScriptRegistration(writer);
this.ProcessFocus(writer);
}
The ProcessUpdatePanels Method of PageRequestManager
As we discussed, the current page consists of a bunch of UpdatePanel server controls, each of which designates a particular region of the page as a partially updatable region When an asynchronous page postback request arrives — that is, when a request for a partial page rendering arrives, how does the page know which UpdatePanel server controls need updating or refreshing? An UpdatePanel server control needs updating if it meets one or more of the following requirements:
❑ The first substring of the string that the LoadPostData method retrieves from the post collection contains the value of the UniqueID property of the UpdatePanel server control
(Recall from Listing 23-10 that the LoadPostData method assigns this substring to the
_updatePanelRequestUpdate field.) As we discussed earlier in this chapter, this scenario
Trang 26occurs when the server control that caused the current asynchronous page postback resides
inside an UpdatePanel server control whose ChildrenAsTriggers property is set to true
❑ The UpdateMode property of the UpdatePanel server control is set to Always
❑ The Update method of the UpdataPanel server control is explicitly invoked As you can see
from Listing 23-17 , this method simply sets the internal Boolean _explicitUpdate field of
the UpdatePanel server control on which the method is invoked to true , to specify that this
UpdatePanel server control must be updated
Use the Update method to imperatively force an UpdatePanel server control to refresh regardless of
what triggered the current asynchronous page postback
If you call the Update method on an UpdatePanel server control whose UpdateMode property is set
to Always , the method will raise an invalid operation exception
❑ The second substring of the string that the LoadPostData method retrieves from the post
collection contains the value of the UniqueID property of the associated server control of
one of the triggers in the Triggers collection of the UpdatePanel server control
Recall from Listing 23-10 that the LoadPostData method assigns this substring to the
_asyncPostBackSourceElementID field
❑ The UpdatePanel server control is a child of an UpdatePanel server control that needs updating
As you can see, the update of an UpdatePanel server control automatically triggers the updates of all its
descendant UpdatePanel server controls However, the update of an UpdatePanel server control does
not automatically trigger the updates of its ancestor UpdatePanel server controls If you want to force
the ancestor UpdatePanel server controls to update as well, you must take one of these extra steps
❑ Set the UpdateMode properties of the ancestor UpdatePanel server controls to Always
❑ Add the triggers that trigger the update of a child UpdatePanel server control to the Triggers
collections of the ancestor UpdatePanel server controls
❑ Imperatively call the Update methods of the ancestor UpdatePanel server controls
Listing 23-17: The Update Method of the UpdatePanel Server Control
public void Update()
PageRequestManager instance calls the ProcessUpdatePanels method on the current
server-side PageRequestManager instance to determine which UpdatePanel server controls on the current
page must be updated Recall that the current server-side PageRequestManager instance maintains
the list of all UpdatePanel server controls on the current page in an internal collection named
_allUpdatePanels
Trang 27As you can see from Listing 23-18 , the ProcessUpdatePanels method iterates through the UpdatePanel server controls in this collection and takes the following steps for each enumerated UpdatePanel server control to determine whether the control needs refreshing (Note that this method sets a local Boolean variable named updatePanelNeedsToUpdate to specify whether the enumerated UpdatePanel server control needs updating.)
Listing 23-18: The ProcessUpdatePanels Method of the PageRequestManager
private void ProcessUpdatePanels(){
if (this._allUpdatePanels != null) {
this._updatePanelsToRefresh = new List<UpdatePanel>(this._allUpdatePanels.Count); this._childUpdatePanelsToRefresh =
new List<UpdatePanel>(this._allUpdatePanels.Count); HtmlForm form1 = this._owner.Page.Form;
for (int num1 = 0; num1 < this._allUpdatePanels.Count; num1++) {
UpdatePanel panel1 = this._allUpdatePanels[num1];
bool updatePanelNeedsToUpdate = false;
if ((this._updatePanelRequiresUpdate != null) &&
string.Equals(panel1.UniqueID, this._updatePanelRequiresUpdate)) updatePanelNeedsToUpdate = true;
else updatePanelNeedsToUpdate = panel1.RequiresUpdate;
Control control1 = panel1.Parent;
while (control1 != form1) {
UpdatePanel panel2 = control1 as UpdatePanel;
if ((panel2 != null) &&
(this._updatePanelsToRefresh.Contains(panel2) ||
this._childUpdatePanelsToRefresh.Contains(panel2))) {
updatePanelNeedsToUpdate = false;
this._childUpdatePanelsToRefresh.Add(panel1);
break;
} control1 = control1.Parent;
if (control1 == null) {
updatePanelNeedsToUpdate = false;
break;
} }
if (updatePanelNeedsToUpdate) {
panel1.SetAsyncPostBackMode(true);
this._updatePanelsToRefresh.Add(panel1);
} else panel1.SetAsyncPostBackMode(false);
} }}
Trang 28The ProcessUpdatePanels method first checks whether the _updatePanelRequiresUpdate field of
the current server-side PageRequestManager instance contains the UniqueID property value of the
enumerated UpdatePanel server control Recall from Listing 23-10 that the LoadPostData method of
the current server-side PageRequestManager instance retrieves from the posted data the UniqueID
property value of the UpdatePanel server control that requires refreshing (if any) and stores the value in
the _updatePanelRequestUpdate field of the current server-side PageRequestManager instance
if ((this._updatePanelRequiresUpdate != null) &&
string.Equals(panel1.UniqueID, this._updatePanelRequiresUpdate))
updatePanelNeedsToUpdate = true;
If this check fails — that is, if the _updatePanelRequiresUpdate field does not contain the UniqueID
property value of the enumerated UpdatePanel server control — the ProcessUpdatePanels method
sets the value of the updatePanelNeedsToUpdate Boolean variable to the value of the RequiresUpdate
property of the enumerated UpdatePanel server control
else
updatePanelNeedsToUpdate = panel1.RequiresUpdate;
Next, I’ll digress from our discussion of the implementation of the ProcessUpdatePanels method to
discuss the implementation of the RequiresUpdate property of the UpdatePanel server control
As Listing 23-19 shows, the RequiresUpdate property returns true if:
❑ The _explicitUpdate field of the UpdatePanel server control is set to true Recall from
List-ing 23-17 that the value of this field is set to true only when you explicitly invoke the Update
method of the UpdatePanel server control from within your code
❑ The UpdateMode property of the UpdatePanel server control is set to Always
❑ The HasTriggered method of the Triggers collection of the UpdatePanel server control returns
true As Listing 23-20 shows, the HasTriggered method of this collection returns true if the
HasTriggered method of at least one of its UpdatePanelTrigger objects returns true (I
dis-cussed the HasTriggered method of the AsyncPostBackTrigger class in Chapter 21 )
Listing 23-19: The RequiresUpdate Method of the UpdatePanel Server Control
protected internal virtual bool RequiresUpdate
Trang 29Listing 23-20: The HasTriggered Method of the UpdatePanelTriggerCollection Class
internal bool HasTriggered(){
using (IEnumerator<UpdatePanelTrigger> enumerator1 = base.GetEnumerator()) {
while (enumerator1.MoveNext()) {
if (enumerator1.Current.HasTriggered()) return true;
} } return false;
UpdatePanel server control to determine whether it resides inside another UpdatePanel server control
If so, it checks whether the _updatePanelsToRefresh or _childUpdatePanelsToRefresh collection
of the current server-side PageRequestManager instance already contains the container UpdatePanel server control If so, this indicates that the container UpdatePanel server control of the enumerated
UpdatePanel server control needs to update Since updating the container UpdatePanel server control automatically updates all its content, including the enumerated UpdatePanel server control, the
ProcessUpdatePanels method first sets the updatePanelNeedsToUpdate local Boolean variable
to false to signal that the enumerated UpdatePanel server control mustn’t be added to the
_updatePanelsToRefresh collection This avoids duplicate updates The ProcessUpdatePanels method then adds the enumerated UpdatePanel server control to the _childUpdatePanelsToRefresh collection
while (control1 != form1) {
UpdatePanel panel2 = control1 as UpdatePanel;
if ((panel2 != null) &&
(this._updatePanelsToRefresh.Contains(panel2) ||
this._childUpdatePanelsToRefresh.Contains(panel2))) {
updatePanelNeedsToUpdate = false;
this._childUpdatePanelsToRefresh.Add(panel1);
break;
} control1 = control1.Parent;
if (control1 == null) {
updatePanelNeedsToUpdate = false;
break;
} }
Trang 30Finally, the ProcessUpdatePanels method checks whether the updatePanelNeedsToUpdate variable
is set to true If so, it first calls the SetAsyncPostBackMode method on the enumerated UpdatePanel
server control and then adds the control to the _updatePanelsToRefresh collection of the current
server-side PageRequestManager instance As Listing 23-21 shows, the SetAsyncPostBackMode
method sets an internal flag of the specified UpdatePanel server control, named _asyncPostBackMode ,
Recall from Listing 21-31 that the markup text that makes up an UpdatePanel server
control consists of two main parts The first part is a div or span HTML element The
second is the rest of the markup text that makes up the UpdatePanel server control
The first part is known as the containing or outermost HTML element because it contains
or encapsulates the second part The second part is known as content because it is
con-tained or enclosed within the opening and closing tags of the containing or outermost
HTML element
As you’ll see later in this chapter, the current server-side PageRequestManager
instance renders the content of an UpdatePanel server control that needs updating
into a string, which is then sent back to the current client-side PageRequestManager
instance for processing
As you’ll see in the next chapter, the current client-side PageRequestManager instance
simply assigns the string that contains the content of the UpdatePanel server control
to the innerHTML property of the containing or outermost HTML element of the
UpdatePanel server control This means that the original content of the UpdatePanel
server control is completely wiped out and replaced with the new content This has an
important consequence when the UpdatePanel server control contains child
UpdatePanel server controls, because updating the UpdatePanel server control
deletes the current child UpdatePanel server controls and replaces them with
brand-new child UpdatePanel server controls, even though the new child UpdatePanel
server controls have the same UniqueID and ClientID property values as the
deleted ones
Therefore, as far as the current client-side PageRequestManager instance is concerned,
when an UpdatePanel server control updates, its child UpdatePanel server controls
do not Instead they are completely deleted from the current page and replaced with
brand-new child UpdatePanel server controls with the same UniqueID and ClientID
property values as the deleted ones
So far, we’ve assumed that the new content simply deleted the old child UpdatePanel
server controls and replaced them with the brand-new child UpdatePanel server controls
with the same UniqueID and ClientID property values as the deleted ones However, it
is quite possible that the new content may also include new child UpdatePanel server
controls that are not replacing the old ones It is also quite possible that some of the old
child UpdatePanel server controls are indeed deleted from the new content, which
means that the new content may have fewer child server controls than the old
Trang 31If the updatePanelNeedsToUpdate variable is set to false , the ProcessUpdatePanels method calls the SetAsyncPostBackMode method on the enumerated UpdatePanel server control to set its
_asyncPostBackMode field to false
else panel1.SetAsyncPostBackMode(false);
As you’ll see later, when the UpdatePanel server control enters its rendering phase, it checks the value
of its _asyncPostBackMode field to determine how to render its content
Listing 23-21: The SetAsyncPostBackMode Method of the UpdatePanel
internal void SetAsyncPostBackMode(bool asyncPostBackMode){
if (this._asyncPostBackModeInitialized) throw new InvalidOperationException(“SetPartialRenderingModeCalledOnce”);
this._asyncPostBackMode = asyncPostBackMode;
this._asyncPostBackModeInitialized = true;
}
RenderControl Method of HtmlForm
Recall from Listing 23-16 that the RenderPageCallback method creates a delegate of type
RenderMethod that represents the RenderFormCallback method of the current server-side
PageRequestManager instance and calls the SetRenderMethodDelegate method on the HtmlForm server control that represents the <form runat=”server”> HTML element of the current page to register this RenderMethod delegate with the HtmlForm server control
Also recall from Listing 23-16 that the RenderPageCallback method calls the RenderControl method
on the HtmlForm server control to have this server control render itself to the ParserHtmlTextWriter instance passed into the RenderControl method as its argument Listing 23-22 presents the internal implementation of the RenderControl method of the HtmlForm server control As you can see, this method calls the RenderControl method of the Control base class, which in turn calls the Render method of the Control base class As Listing 23-13 shows, the Render method of the Control base class calls the RenderChildren method
Listing 23-22: The RenderControl Method of the HtmlForm Server Control
public override void RenderControl(HtmlTextWriter writer){
if (base.DesignMode) base.RenderChildren(writer);
else base.RenderControl(writer);
}
Listing 23-23 presents the internal implementation of the RenderChildren method of the HtmlForm server control This method first invokes the OnFormRender method on the Page :
this.Page.OnFormRender();
Trang 32Next, it calls the BeginFormRender method on the Page :
Listing 23-23: The RenderChildren Method of the HtmlForm Server Control
protected internal override void RenderChildren(HtmlTextWriter writer)
The RenderChildren method of the Control base class (see Listing 23-14 ) calls the GetRenderMethod
to return a reference to the RenderMethod delegate that represents the RenderFormCallback method
and calls this delegate, and consequently the RenderFormCallback method, bypassing the normal
rendering logic of the child controls of the HtmlForm server control Recall that this normal logic simply
iterates through the child controls in the Controls collection of the HtmlForm server control and calls
the RenderControl method on each child control, as shown in the boldface portion of Listing 23-14
The RenderFormCallback Method of PageRequestManager
Listing 23-24 presents the internal implementation of the RenderFormCallback method of the
server-side PageRequestManager Recall that the ProcessUpdatePanels method populates an internal
collection named _updatePanelsToRefresh with all the UpdatePanel server controls that need
refreshing The RenderFormCallback method iterates through the UpdatePanel server controls in this
collection and calls the RenderControl method on each enumerated UpdatePanel server control to have
the control render itself into the HtmlTextWriter object that the _updatePanelWriter field references
Recall from Listing 23-16 that the RenderPageCallback method of the PageRequestManager
stores a reference to the HtmlTextWriter object that wraps the response output stream in the
_updatePanelWriter field Therefore, the RenderControl method of the enumerated UpdatePanel
Trang 33server control ends up rendering itself into the response output stream The response output stream is the stream that contains the response text that is sent back to the requesting browser
As you can see, the RenderFormCallback method renders the UpdatePanels in the
_updatePanelsToRefresh collection only In other words, none of the other server controls on the current page is rendered when the current request is an asynchronous page postback The other server controls go through all their life cycle phases as usual, except for the rendering phase This phase is what makes an asynchronous page postback request different from a synchronous page postback request
While the HTML markup contained in the response output stream in the case of a synchronous page postback contains HTML markup text from all visible server controls on the current page, the HTML markup text contained in the response output stream in the case of an asynchronous page postback contains HTML markup text only from the visible UpdatePanel server controls in the
_updatePanelsToRefresh collection
Listing 23-24: The RenderFormCallback Method of the PageRequestManager
private void RenderFormCallback(HtmlTextWriter writer, Control containerControl){
PageRequestManager.ParserStringWriter writer1 = writer.InnerWriter as PageRequestManager.ParserStringWriter;
writer1.ParseWrites = false;
if (this._updatePanelsToRefresh != null) {
foreach (UpdatePanel panel1 in this._updatePanelsToRefresh) {
if (panel1.Visible) panel1.RenderControl(this._updatePanelWriter);
} } writer1.ParseWrites = true;
}
The RenderControl Method of the UpdatePanel
The UpdatePanel server control inherits the RenderControl method from the Control base class The base class’s implementation of the RenderControl method calls the Render method shown in Listing 23-25 As you can see, this method calls the VerifyRenderingInServerForm method on the Page object to raise an exception if the UpdatePanel server control is not inside a <form runat=”server”> element:
this.IPage.VerifyRenderingInServerForm(this);
Next, it calls the Render method of its base class, which in turn calls the RenderChildren method
Listing 23-25: The Render Method of the UpdatePanel
protected override void Render(HtmlTextWriter writer){
this.IPage.VerifyRenderingInServerForm(this);
base.Render(writer);
}
Trang 34Listing 23-26 presents the implementation of the RenderChildren method of the UpatePanel server
control This method checks whether the UpdatePanel control is in asynchronous postback mode
Recall from Listing 23-21 that the ProcessUpdatePanels method calls the SetAsyncPostBackMode
method on each UpdatePanel server control in the _allUpdatePanels collection of the current
server-side PageRequestManager instance to set the value of the _asyncPostBackMode field of the
UpdatePanel server control to specify whether the UpdatePanel server control must be rendered in
asynchronous postback mode
As Listing 23-26 shows, the RenderChildren method of an UpdatePanel server control instantiates an
HtmlTextWriter instance when the control is in asynchronous postback mode :
HtmlTextWriter writer1 = new HtmlTextWriter(new StringWriter());
Next, it calls the RenderChildren method of its base class — that is, the Control base class — passing
in the HtmlTextWriter instance Recall from Listing 23-14 that the RenderChildren method of the
Control base class iterates through the child controls in the Controls collection and invokes the
RenderControl method on each child control to have the child control render itself into the preceding
HtmlTextWriter instance In other words, this HtmlTextWriter instance accumulates the HTML
markup text generated by the server controls and HTML enclosed within the opening and closing tags of
the <ContentTemplate> child element of the <UpdatePanel> tag
base.RenderChildren(writer1);
Finally, the RenderChildren method of the UpdatePanel control calls the EncodeString static
method on the server-side PageRequestManager class, passing in four parameters: the first references
the HtmlTextWriter instance that wraps the response output stream, the second is the string
“updatePanel” , the third contains the value of the ClientID property of the UpdatePanel server
con-trol, and the last is a string that contains all the HTML markup of the child controls of the UpdatePanel
server control The main responsibility of the EncodeString method is to encode the last three
parameters into a string and render the string into the HtmlTextWriter object that the first parameter
references — that is, the HtmlTextWriter instance that wraps the response output stream
Listing 23-26: The RenderChildren Method of the UpdatePanel
protected override void RenderChildren(HtmlTextWriter writer)
Trang 35if (this.RenderMode == UpdatePanelRenderMode.Block) writer.RenderBeginTag(HtmlTextWriterTag.Div);
else writer.RenderBeginTag(HtmlTextWriterTag.Span);
base.RenderChildren(writer);
writer.RenderEndTag();
} this._rendered = true;
}
The GetAsyncPostBackControlIDs Method of the Server-Side PageRequestManager
Listing 23-27 presents the implementation of the GetAsyncPostBackControlIDs method of the
PageRequestManager As you can see, this method simply delegates to the GetControlIDsFromList method, passing in the _asyncPostBackControls collection Recall that the PageRequestManager server class features a private collection named _asyncPostBackControl that contains all the server controls that cause asynchronous page postbacks
Listing 23-27: The GetAsyncPostBackControlIDs Method of the PageRequestManager
private string GetAsyncPostBackControlIDs(bool includeQuotes){
return PageRequestManager.GetControlIDsFromList(this._asyncPostBackControls, includeQuotes);
}
The GetControlIDsFromList Method of the Server-Side PageRequestManager
Listing 23-28 contains the code for the GetControlIDsFromList method of the server-side
PageRequestManager This method takes a List<Control> collection that contains a list of server controls The main responsibility of this method is to return a comma-separated list of strings, each of which contains the UniqueID property value of a server control in the List<Control> collection Note that this method takes a second Boolean argument that specifies whether these UniqueID property values must be rendered in quotes
Listing 23-28: The GetControlIDsFromList Method of the PageRequestManager
private static string GetControlIDsFromList(List<Control> list, bool includeQuotes){
if ((list == null) || (list.Count <= 0)) return string.Empty;
StringBuilder builder1 = new StringBuilder();
bool flag1 = true;
for (int num1 = 0; num1 < list.Count; num1++) {
if (list[num1].Visible) {
if (!flag1) builder1.Append(“,”);
(continued)
Trang 36The GetPostBackControlIDs Method of the Server-Side PageRequestManager
Listing 23-29 presents the implementation of the GetPostBackControlIDs method of the server-side
PageRequestManager As you can see, this method simply delegates to the GetControlIDsFromList
method, passing in the _postBackControls collection Recall that the PageRequestManager features a
private collection field named _postBackControls that contains all the server controls on the current
page that cause synchronous page postbacks
Listing 23-29: The GetPostBackControlIDs Method of the PageRequestManager
private string GetPostBackControlIDs(bool includeQuotes)
Recall from Listing 23-16 that the RenderPageCallback method of the server-side PageRequestManager
calls the GetAllUpdatePanelIDs method to return a comma-separated list of strings that contain the
values of the UniqueID properties of all the UpdatePanel server controls on the current page:
PageRequestManager.EncodeString(writer, “updatePanelIDs”, string.Empty,
this.GetAllUpdatePanelIDs());
Listing 23-30 presents the internal implementation of the GetAllUpdatePanelIDs method As you can
see, this method simply delegates to the GetUpdatePanelIDsFromList static method of the server-side
PageRequestManager
Listing 23-30: The GetAllUpdatePanelIDs Method of the PageRequestManager
private string GetAllUpdatePanelIDs()
{
return PageRequestManager.GetUpdatePanelIDsFromList(this._allUpdatePanels, true);
}
Trang 37GetUpdatePanelIDsFromList method takes a second Boolean argument that specifies whether each substring must contain the first part — that is, the letter f or t
As you can see from Listing 23-31 , the GetUpdatePanelIDsFromList method instantiates a
StringBuilder , iterates through the UpdatePanel server controls in the List<UpdatePanel>
collection, and appends a substring to the StringBuilder for each UpdatePanel server control Note that the content of the first part of this substring depends on the value of the ChildrenAsTriggers property of the associated UpdatePanel server control If this property is set to true , the first part of the substring contains the letter t Otherwise the first part of the substring contains the letter f
Listing 23-31: The GetUpdatePanelIDsFromList Static Method of the PageRequestManager
private static string GetUpdatePanelIDsFromList(List<UpdatePanel> list, bool includeChildrenAsTriggersPrefix){
if ((list == null) || (list.Count <= 0)) return string.Empty;
StringBuilder builder1 = new StringBuilder();
bool flag1 = true;
for (int num1 = 0; num1 < list.Count; num1++) {
if (list[num1].Visible) {
if (!flag1) builder1.Append(‘,’);
flag1 = false;
if (includeChildrenAsTriggersPrefix) builder1.Append(list[num1].ChildrenAsTriggers ? ‘t’ : ‘f’);
builder1.Append(list[num1].UniqueID);
} } return builder1.ToString();
}
GetChildUpdatePanelIDs
Recall from Listing 23-16 that the RenderPageCallback method of the server-side PageRequestManager calls the GetChildUpdatePanelIDs method to return a comma-separated list of strings that contain the values of the UniqueID properties of all the child UpdatePanel server controls on the current page:
PageRequestManager.EncodeString(writer, “childUpdatePanelIDs”, string.Empty, this.GetChildUpdatePanelIDs());