Postbacks and Postback Data

Postbacks and Postback Data

Suppose you wanted to build a custom control similar to the FCL s TextBox control. On the surface, this sounds easy enough. You d start by deriving from Control and overriding its Render method with one that outputs an <input type= text > tag. Most likely, you d also implement a Text property to expose the control s text. Here s what the derived class might look like:

using System; using System.Web.UI; namespace Wintellect { public class MyTextBox : Control { string MyText = ""; public string Text { get { return MyText; } set { MyText = value; } } protected override void Render (HtmlTextWriter writer) { writer.WriteBeginTag ("input"); writer.WriteAttribute ("type", "text"); writer.WriteAttribute ("name", UniqueID); if (ID != null) writer.WriteAttribute ("id", ClientID); if (Text.Length > 0) writer.WriteAttribute ("value", Text); writer.Write (HtmlTextWriter.TagRightChar); } } }

This code sample illustrates a pair of subtle but important points that control developers should take to heart:

  • If a tag output by a control includes a Name attribute, the value of that attribute should be taken from the UniqueID property that the control inherits from Control.

  • If a tag output by a control includes an Id attribute, the value of that attribute should be taken from the ClientID property that the control inherits from Control.

UniqueID and ClientID are important because, unlike the ID property, they re never null. Even if the tag that declares a custom control instance lacks an Id attribute, UniqueID and ClientID assume values defined by the system that give the control a unique identity. And unlike ID, UniqueID and ClientID give each control instance a unique identity even when a replicator-type control (such as a Repeater) is used to create multiple control instances.

Here s a tag that declares a MyTextBox instance in a Web form:

<win:MyTextBox  Text="Bill" RunAt="server" />

And here s the output:

<input type="text" name="UserName"  value="Bill">

So far, there s nothing here that you haven t seen already. But now comes the hard part. When a postback occurs, MyTextBox should update its Text property from the postback data so that a server-side script can read the text typed into the control. In other words, let s say the user types Gates into the text box and submits the form back to the server, generating an HTTP POST with the following message body:

UserName=Gates

The MyTextBox control created on the server must read the message body and update its Text property accordingly. Which brings up a question: how?

The IPostBackDataHandler Interface

The answer is an interface named IPostBackDataHandler, which belongs to the FCL s System.Web.UI namespace. Implementing IPostBackDataHandler enables a control to access data accompanying a postback and update its properties accordingly. The interface defines two methods, both of which must be implemented in a class that derives from it:

  • LoadPostData, which the .NET Framework calls to pass postback data to the control

  • RaisePostDataChangedEvent, which is called after LoadPostData to give the control the opportunity to fire events stemming from changes in its internal state following a postback

Forget about RaisePostDataChangedEvent for a moment; we ll talk about it later. LoadPostData is the method that interests us for now because it s the one we can use to grab the text typed into a text box created by MyTextBox. LoadPostData is prototyped this way:

bool LoadPostData (string postDataKey, NameValueCollection postCollection)

When LoadPostData is called, postCollection holds all the data that accompanied the postback not just for the control whose LoadPostData method was called, but for all controls. The individual data items in postCollection are indexed, and postDataKey holds the index of the data item that corresponds to the control whose LoadPostData method was called. (The index is actually the control ID, but that s an implementation detail that has no bearing on the code you write.) If the control emits an <input type= text > tag and the user types Gates into it, postCollection[postDataKey] equals Gates when LoadPostData is called.

Figure 8-7 contains the source code for a MyTextBox control whose Text property is updated on each and every postback. MyTextBox derives not only from Control but from IPostBackDataHandler. Yes, it s true that a managed type can have only one base class, but it s perfectly legal to derive from one base class and one or more interfaces. All an interface does is define abstract methods that must be overridden in a derived class. The fact that MyTextBox derives from IPostBackDataHandler indicates that it implements the IPostBackDataHandler interface. And because it derives from IPostBackDataHandler, it s obliged to override the LoadPostData and RaisePostDataChangedEvent methods. MyTextBox s LoadPostData method retrieves the postback data generated from the text that the user typed into the input field. Then it writes the value to its own Text property.

MyTextBox1.cs

using System; using System.Web.UI; using System.Collections.Specialized; namespace Wintellect { public class MyTextBox : Control, IPostBackDataHandler { string MyText = ""; public string Text { get { return MyText; } set { MyText = value; } }

 public bool LoadPostData (string postDataKey, NameValueCollection postCollection) { Text = postCollection[postDataKey]; return false; } public void RaisePostDataChangedEvent () { } protected override void Render (HtmlTextWriter writer) { writer.WriteBeginTag ("input"); writer.WriteAttribute ("type", "text"); writer.WriteAttribute ("name", UniqueID); if (ID != null) writer.WriteAttribute ("id", ClientID); if (Text.Length > 0) writer.WriteAttribute ("value", Text); writer.Write (HtmlTextWriter.TagRightChar); } } }

Figure 8-7

MyTextBox control.

You can try out MyTextBox with the Web form in Figure 8-8. First compile MyTextBox1.cs into an assembly named MyTextBoxControl.dll and place it in the application root s bin directory. Then bring up the page in your browser, type something into the text box, and click the Test button. The text that you typed should appear below the text box, proof that the control updated its Text property from the postback data.

MyTextBoxPage1.aspx

<%@ Register TagPrefix="win" Namespace="Wintellect" Assembly="MyTextBoxControl" %> <html> <body> <form runat="server"> <win:MyTextBox  Text="Type something here" RunAt="server" /> <asp:Button Text="Test" OnClick="OnTest" RunAt="server" /><br> <asp:Label  RunAt="server" />

 </form> </body> </html> <script language="C#" runat="server"> void OnTest (Object sender, EventArgs e) { Output.Text = Input.Text; } </script>

Figure 8-8

MyTextBox test page.

Are you surprised by the complexity of MyTextBox? Who would have thought that a control as simple as TextBox the FCL class that MyTextBox is patterned after would have to do so much just to do so little? TextBox is an exemplary class because despite its outward simplicity, its implementation is moderately complex. It also demonstrates some of the key facets of the server control programming model. In the next several sections, we ll enhance MyTextBox until it s practically a plug-in replacement for TextBox. The goal isn t to build a better TextBox: it s to understand the intricacies of control programming using a familiar control type as a baseline.

View State

Most controls fire events of one type or another. For example, Button controls fire Click events, and ListBoxes fire SelectedIndexChanged events. TextBoxes fire TextChanged events when the text submitted via a postback doesn t match the text that the TextBox returned to the browser.

Sounds simple, right? But there s a huge gotcha waiting to ensnare the unwary developer. A server control s lifetime matches that of a single HTTP request. If MyTextBoxPage.aspx is requested 100 times, ASP.NET creates 100 different MyTextBox objects to fulfill those requests. Because MyTextBox is reinstantiated each and every time a request arrives, it can t very well use data members to store state from one request to the next. In other words, MyTextBox can t possibly determine whether its text has changed by doing this:

public bool LoadPostData (string postDataKey, NameValueCollection postCollection) { string NewText = postCollection[postDataKey]; if (NewText != MyText) { // The control's text has changed } return false; } 

Why? Because MyText won t necessarily equal what you set it to in the last request. Instead, it will hold the value it was initialized with when the control was instantiated.

One of the most difficult aspects of building stateful programs on top of stateless protocols is figuring out how to hold state between requests. That s why ASP.NET offers a mechanism called view state. View state is a place where controls can store state in such a way that it remains valid from one request to the next. It s particularly useful for controls that fire change events and that therefore require a mechanism for retaining state across requests. ASP.NET does the hard part by storing the state. Your job is to tell it what to store.

ASP.NET exposes its view state mechanism through a Control property named ViewState. The property s type is StateBag, which is a dictionary-like class that stores key/value pairs. The following statement adds an integer to view state and keys it with the string Count :

ViewState["Count"] = 1;

The next statement reads the integer back from view state. The cast is necessary because view state is typed to store generic Objects:

int count = (int) ViewState["Count"];

The magic here is that Count can be written to view state in one request and read back in the next one. Therefore, view state is an exceedingly easy-to-use means for persisting a control s internal state from one page invocation to the next.

Change Events

Now that you know about view state, you re prepared for the next logical step in MyTextBox s evolution: adding TextChanged events. Declaring an event is no big deal; events were discussed briefly in Chapter 2 and again in Chapter 7. The following statement declares an event named TextChanged whose type is EventHandler. Recall that EventHandler is one of the standard delegates defined in the System namespace:

public event EventHandler TextChanged;

With the event thusly declared, firing a TextChanged event is as simple as this:

if (TextChanged != null) TextChanged (this, new EventArgs ());

The question that remains is when to fire the event. That s where IPostBackDataHandler s other method, RaisePostDataChangedEvent, comes in.

RaisePostDataChangedEvent exists for the sole purpose of enabling controls that update their properties from postback data to fire change events. RaisePostDataChangedEvent is called right after LoadPostData, but it s called only if LoadPostData returns true. In Figure 8-7, RaisePostDataChangedEvent contains no code because it s never called (note LoadPostData s return value). Here s the proper way to implement LoadPostData and RaisePostDataChangedEvent in controls that support change events:

  • Persist property values that serve as the basis for change events in view state.

  • In LoadPostData, extract the new property values from the postback data and the old property values from view state and then compare the two. If a property changed, return true so that RaisePostDataChangedEvent will be called. Otherwise, return false.

  • In RaisePostDataChangedEvent, fire your change events.

Figure 8-9 contains a modified version of MyTextBox that fires TextChanged events. The data member MyText is gone; it s no longer needed now that Text is stored in view state. LoadPostData has been modified to compare the old text to the new and return true if they re unequal. And RaisePostDataChangedEvent fires a TextChanged event to let interested parties know that a change occurred.

MyTextBox2.cs

using System; using System.Web.UI; using System.Collections.Specialized; namespace Wintellect { public class MyTextBox : Control, IPostBackDataHandler { public event EventHandler TextChanged; public string Text { get { string text = (string) ViewState["MyText"]; return (text == null) ? "" : text; }

 set { ViewState["MyText"] = value; } } public bool LoadPostData (string postDataKey, NameValueCollection postCollection) { string temp = Text; Text = postCollection[postDataKey]; return (temp != Text); } public void RaisePostDataChangedEvent () { if (TextChanged != null) TextChanged (this, new EventArgs ()); // Fire event } protected override void Render (HtmlTextWriter writer) { writer.WriteBeginTag ("input"); writer.WriteAttribute ("type", "text"); writer.WriteAttribute ("name", UniqueID); if (ID != null) writer.WriteAttribute ("id", ClientID); if (Text.Length > 0) writer.WriteAttribute ("value", Text); writer.Write (HtmlTextWriter.TagRightChar); } } }

Figure 8-9

MyTextBox control with TextChanged events.

The Web form in Figure 8-10 responds to TextChanged events by displaying a message underneath the control. To see for yourself, open MyTextBoxPage2.aspx in your browser (don t forget to regenerate MyTextBoxControl.dll by compiling MyTextBox2.cs first) and click the Test button to force a postback. Nothing visible should happen because the input text didn t change. Now edit Type something here and click Test again. This time, Text changed should appear under the control, demonstrating that it fired a TextChanged event.

MyTextBoxPage2.aspx

<%@ Register TagPrefix="win" Namespace="Wintellect" Assembly="MyTextBoxControl" %> <html> <body> <form runat="server"> <win:MyTextBox  Text="Type something here" OnTextChanged="OnTextChanged" RunAt="server" /> <asp:Button Text="Test" RunAt="server" /><br> <asp:Label  RunAt="server" /> </form> </body> </html> <script language="C#" runat="server"> void Page_Load (Object sender, EventArgs e) { Output.Text = ""; // Reset the Label control } void OnTextChanged (Object sender, EventArgs e) { Output.Text = "Text changed"; } </script>
Figure 8-10

Revised test page for MyTextBox.

How View State Works

Are you curious to know how ASP.NET saves the data that you write to view state? Bring up MyTextBoxPage2.aspx in your browser, click the Test button, and check out the HTML that comes back. Here s what you ll see:

<html> <body> <form name="_ctl0" method="post" action="mytextboxpage2.aspx" > <input type="hidden" name="__VIEWSTATE"  value="dDwtNzIwNTMyODUzO3Q8O2w8aTwxPjs+O2w8dDw7bDxpPDE+O2k8NT47PjtsPH Q8cDxsPE15VGV4dDs+O2w8VHlwZSBzb21ldGhpbmcgaGVyZTs+Pjs7Pjt0PHA8cDxsPFR leHQ7PjtsPFxlOz4+Oz47Oz47Pj47Pj47Pg==" /> <input type="text" name="Input" value="Type something here"> <input type="submit" name="_ctl1" value="Test" /><br> <span ></span> </form> </body> </html>

The key is the hidden input control named __VIEWSTATE. It doesn t show up in the Web page because it s marked type= hidden. It has no UI because it doesn t need one; it s there for the sole purpose of round-tripping view state to the client and back. View state isn t stored on the Web server. It s transmitted to the client in a hidden control and then transmitted back to the server as part of the form s postback data. The value of __VIEWSTATE is a base-64 encoded version of all the data written to view state by all the page s controls, plus any view state saved by the page itself, plus a hash value generated from the page s contents that enables ASP.NET to detect changes to the page.

This answers one of the most common questions that newcomers ask about ASP.NET: What s all that __VIEWSTATE stuff I see when I do a View/Source? Now you know. That s how ASP.NET components persist state across round trips. View state is typically used to detect changes in control state, but it has other uses, too. I once used it to help a sortable DataGrid remember which column it was last sorted on. I didn t have to modify DataGrid. In the page that hosted the DataGrid, I simply wrote a sort expression to view state when the DataGrid was sorted and retrieved it from view state whenever I needed it. Because Page derives indirectly from Control, and because pages are instances of classes derived from Page, pages can access view state using the same ViewState property that controls use.

View State Security

If you submit a Web form over an unencrypted channel, it s entirely conceivable that someone could intercept the view state accompanying the request and modify it, possibly for malicious purposes. To guard against such occurrences without resorting to HTTPS, include the following statement at the top of your ASPX files:

<%@ Page EnableViewStateMac="true" %>

The Mac in EnableViewStateMac stands for message authentication code. Setting EnableViewStateMac to true appends a hash of view state combined with a validation key to every __VIEWSTATE value returned from this page. Following a postback, ASP.NET verifies that view state wasn t tampered with by rehashing it and comparing the new hash value to the one round-tripped to the client. A snooper can t alter __VIEWSTATE and escape detection without updating the hash too. But updating the hash is next to impossible because the validation key is known only to the server.

EnableViewStateMac ensures that alterations don t go unnoticed, but it doesn t protect view state from prying eyes or physically prevent it from being altered. For an extra measure of security over unencrypted connections, add the following entry to your Web server s Machine.config file:

<machineKey validation="3DES" />

Henceforth, ASP.NET will encrypt view state using symmetric Triple DES encryption. Encrypted view state can t be read by mere mortals unless they manage to compromise your Web server and steal the encryption key. That key is randomly generated by ASP.NET (unless you specify otherwise using additional entries in Machine.config) and stored by the Web server s Local Security Authority (LSA).

Generating Postbacks

MyTextBox is now a reasonable facsimile of TextBox, but it still lacks an important ingredient: an AutoPostBack property. Setting AutoPostBack to true programs a TextBox to fire a TextChanged event the moment it loses the input focus following a text change. Without AutoPostBack, TextChanged events don t fire until the page posts back to the server for some other reason, such as a button click. AutoPostBack forces the postback to occur immediately. How it forces postbacks isn t obvious to the casual observer. Rather than explain how AutoPostBack works and follow up with a code sample, I ll show you the code first and then explain how it works.

Figure 8-11 contains the third and final version of MyTextBox. Unlike the previous versions, this one implements a public property named AutoPostBack whose value is stored in a private field (MyAutoPostBack). The latest version also implements Render differently: it adds an OnChange attribute to the <input type= text > tag. Here s the relevant code:

if (AutoPostBack) writer.WriteAttribute ("onchange", "javascript:" + Page.GetPostBackEventReference (this));

Execute a View/Source command after fetching MyTextBoxPage3.aspx (Figure 8-12) and you ll see this:

<input type="text" name="Input"  value="Type something here" onchange="javascript:__doPostBack('Input','')"> . . . <input type="hidden" name="__EVENTTARGET" value="" /> <input type="hidden" name="__EVENTARGUMENT" value="" /> <script language="javascript"> <!-- function __doPostBack(eventTarget, eventArgument) { var theform = document._ctl0; theform.__EVENTTARGET.value = eventTarget; theform.__EVENTARGUMENT.value = eventArgument; theform.submit(); } // --> </script>

See how it works? The OnChange attribute designates a handler for DHTML OnChange events. A text input field fires an OnChange event when it loses the input focus following a text change. Page.GetPostBackEventReference returns code that calls a JavaScript function named __doPostBack. It also writes __doPostBack to a script block returned to the client. When the __doPostBack function is called in response to an OnChange event, it programmatically submits the form to the server by calling submit on the DHTML object representing the form (theform). In other words, AutoPostBack works its magic with some clever client-side script. And the script is simple enough that it works with just about any browser that supports JavaScript.

MyTextBox3.cs

using System; using System.Web.UI; using System.Collections.Specialized; namespace Wintellect { public class MyTextBox : Control, IPostBackDataHandler { bool MyAutoPostBack = false; public event EventHandler TextChanged; public string Text { get { string text = (string) ViewState["MyText"]; return (text == null) ? "" : text; } set { ViewState["MyText"] = value; } }

 public bool AutoPostBack { get { return MyAutoPostBack; } set { MyAutoPostBack = value; } } public bool LoadPostData (string postDataKey, NameValueCollection postCollection) { string temp = Text; Text = postCollection[postDataKey]; return (temp != Text); } public void RaisePostDataChangedEvent () { if (TextChanged != null) TextChanged (this, new EventArgs ()); // Fire event } protected override void Render (HtmlTextWriter writer) { writer.WriteBeginTag ("input"); writer.WriteAttribute ("type", "text"); writer.WriteAttribute ("name", UniqueID); if (ID != null) writer.WriteAttribute ("id", ClientID); if (Text.Length > 0) writer.WriteAttribute ("value", Text); if (AutoPostBack) writer.WriteAttribute ("onchange", "javascript:" + Page.GetPostBackEventReference (this)); writer.Write (HtmlTextWriter.TagRightChar); } } }

Figure 8-11

MyTextBox control with AutoPostBack.

You can try out the AutoPostBack property with the Web page in Figure 8-12. MyTextBoxPage3.aspx is identical to MyTextBoxPage2.aspx save for the AutoPostBack attribute in the <win:MyTextBox> tag. Press the Tab key a few times to move the input focus around on the page. Then tab to the MyTextBox control, edit its text, and press Tab again. This time, Text changed should appear underneath the control indicating that a TextChanged event fired this despite the fact that you didn t click the Test button to submit the form to the server.

MyTextBoxPage3.aspx

<%@ Register TagPrefix="win" Namespace="Wintellect" Assembly="MyTextBoxControl" %> <html> <body> <form runat="server"> <win:MyTextBox  Text="Type something here" OnTextChanged="OnTextChanged" AutoPostBack="true" RunAt="server" /> <asp:Button Text="Test" RunAt="server" /><br> <asp:Label  RunAt="server" /> </form> </body> </html> <script language="C#" runat="server"> void Page_Load (Object sender, EventArgs e) { Output.Text = ""; // Reset the Label control } void OnTextChanged (Object sender, EventArgs e) { Output.Text = "Text changed"; } </script>
Figure 8-12

Final test page for MyTextBox.

The IPostBackEventHandler Interface

IPostBackDataHandler is for controls that update their properties from postback data. Because FCL controls such as TextBox, CheckBox, RadioButton, and ListBox all wrap HTML elements that transmit postback data, all implement the IPostBackDataHandler interface, too.

The .NET Framework defines a second postback-related interface named IPostBackEventHandler. Not to be confused with IPostBackDataHandler, IPostBackEventHandler enables controls that generate postbacks to be notified when they cause postbacks to occur. LinkButton is one example of an FCL control that implements IPostBackEventHandler. Its server-side processing regimen includes firing Click and Command events, but only if it was the LinkButton that caused the postback to occur in the first place.

IPostBackEventHandler s sole method, RaisePostBackEvent, is called by ASP.NET when a control that implements IPostBackEventHandler posts a page back to the server. RaisePostBackEvent is prototyped this way:

void RaisePostBackEvent (string eventArgument)

The one and only parameter passed to RaisePostBackEvent is the second parameter passed to __doPostBack to generate the postback. Here s the line again in MyTextBox that adds an OnChange attribute to the <input> tag when AutoPostBack is true:

writer.WriteAttribute ("onchange", "javascript:" + Page.GetPostBackEventReference (this));

And here s the resulting output:

onchange="javascript:__doPostBack('Input','')"

In this example, RaisePostBackEvent s eventArgument parameter is an empty string because __doPostBack s second parameter is an empty string. But suppose you called GetPostBackEventReference this way:

writer.WriteAttribute ("onchange", "javascript:" + Page.GetPostBackEventReference (this, "Hello"));

Now the OnChange attribute looks like this:

onchange="javascript:__doPostBack('Input','Hello')"

And RaisePostBackEvent can read the string from its parameter list:

void RaisePostBackEvent (string eventArgument) { string arg = eventArgument; // "Hello" }

The ability to pass application-specific data to controls that generate postbacks comes in handy when the action taken by the control in RaisePostBackEvent depends on the action that generated the postback on the client side. You ll see what I mean in the next section. But first let s see a real-world example of IPostBackEventHandler in action.

The source code in Figure 8-13 is that of a control named MyLinkButton. Like its FCL namesake, LinkButton, it creates a hyperlink that posts back to the server. When the postback occurs, the control fires a Click event. Here are two important elements of its design:

  • MyLinkButton s Render method emits a text string surrounded by an HTML <a> element. The element s Href attribute points to __doPostBack, which submits the form to the server.

  • MyLinkButton implements IPostBackEventHandler. When the postback occurs, ASP.NET calls the control s RaisePostBackEvent method, which in turn fires a Click event. If the page contains 100 MyLinkButtons, only one fires a Click event because only one has its RaisePostBackEvent method called.

Figure 8-14 contains a very simple Web form that you can use to test MyLinkButton. Because the @ Register directive refers to an assembly named MyLinkButtonControl, you need to compile MyLinkButton.cs into a DLL named MyLinkButtonControl.dll before executing the page.

MyLinkButton.cs

using System; using System.Web.UI; namespace Wintellect { public class MyLinkButton : Control, IPostBackEventHandler { string MyText = ""; public event EventHandler Click; public string Text { get { return MyText; } set { MyText = value; } } public void RaisePostBackEvent (string eventArgument) { if (Click != null) Click (this, new EventArgs ()); } protected override void Render (HtmlTextWriter writer) {

 // Output an <a> tag writer.WriteBeginTag ("a"); if (ID != null) writer.WriteAttribute ("id", ClientID); writer.WriteAttribute ("href", "javascript:" + Page.GetPostBackEventReference (this)); writer.Write (HtmlTextWriter.TagRightChar); // Output the text bracketed by <a> and </a> tags if (Text.Length > 0) writer.Write (Text); // Output a </a> tag writer.WriteEndTag ("a"); } } }

Figure 8-13

MyLinkButton control.

MyLinkButtonPage.aspx

<%@ Register TagPrefix="win" Namespace="Wintellect" Assembly="MyLinkButtonControl" %> <html> <body> <form runat="server"> <win:MyLinkButton Text="Click Me" OnClick="OnClick" RunAt="server" /><br> <asp:Label  RunAt="server" /> </form> </body> </html> <script language="C#" runat="server"> void OnClick (Object sender, EventArgs e) { Output.Text = "Click!"; } </script>
Figure 8-14

MyLinkButton test page.

The AutoCounter Control

Let s sum up what you ve learned thus far with a custom control that handles postback data and postback events. The control is named AutoCounter. AutoCounter renders a text box sandwiched between < and > buttons (Figure 8-15). When clicked, < and > increment or decrement the value in the text box and fire an Increment or a Decrement event. Manually typing a number into the text box and submitting the form to the server fires a CountChanged event. AutoCounter exposes one property Count that you can use to read and write the value displayed. Typing a non-numeric value into the control resets the count to 0.

AutoCounter s source code, shown in Figure 8-16, reveals the innermost secrets of its design and operation. There s nothing here you haven t seen before; the difference is that this time, it s all under one roof. AutoCounter implements IPostBackDataHandler in order to update its Count property (and fire a CountChanged event) when the user types a value into the text box and posts back to the server. It also implements IPostBackEventHandler so that it can fire Increment and Decrement events when the < and > buttons are clicked. To generate postbacks, AutoCounter s Render method encloses the < and > in HTML anchor elements (<a>) whose Href attributes point to the __doPostBack function returned by GetPostBackEventReference.

Pay particular attention to how GetPostBackEventReference is called. For the < button, it s called this way:

writer.WriteAttribute ("href", "javascript:" + Page.GetPostBackEventReference (this, "dec"));

And for the > button, it s called like this:

writer.WriteAttribute ("href", "javascript:" + Page.GetPostBackEventReference (this, "inc"));

RaisePostBackEvent uses the second parameter passed to GetPostBackEventReference to determine whether to fire an Increment event or a Decrement event:

if (eventArgument == "dec") { ... if (Decrement != null) Decrement (this, new EventArgs ()); } else if (eventArgument == "inc") { ... if (Increment != null) Increment (this, new EventArgs ()); }

This is a great example of how GetPostBackEventReference s optional second parameter can be used on the server to determine what generated the postback when a control emits multiple postback elements.

Figure 8-15

AutoCounter control in action.

AutoCounter.cs

using System; using System.Web.UI; using System.Collections.Specialized; namespace Wintellect { public class AutoCounter : Control, IPostBackDataHandler, IPostBackEventHandler { public event EventHandler Decrement; public event EventHandler Increment; public event EventHandler CountChanged; public int Count { get { int count = 0; if (ViewState["Count"] != null) count = (int) ViewState["Count"]; return count; } set { ViewState["Count"] = value; } } public bool LoadPostData (string postDataKey, NameValueCollection postCollection)

 { int temp = Count; try { Count = Convert.ToInt32 (postCollection[postDataKey]); } catch (FormatException) { Count = 0; } return (temp != Count); } public void RaisePostDataChangedEvent () { if (CountChanged != null) CountChanged (this, new EventArgs ()); } public void RaisePostBackEvent (string eventArgument) { if (eventArgument == "dec") { Count--; if (Decrement != null) Decrement (this, new EventArgs ()); } else if (eventArgument == "inc") { Count++; if (Increment != null) Increment (this, new EventArgs ()); } } protected override void Render (HtmlTextWriter writer) { // Output an <a> tag writer.WriteBeginTag ("a"); writer.WriteAttribute ("href", "javascript:" + Page.GetPostBackEventReference (this, "dec")); writer.Write (HtmlTextWriter.TagRightChar); // Output a less-than sign writer.Write ("&lt;"); // Output a </a> tag writer.WriteEndTag ("a"); // Output an <input> tag writer.Write (" "); writer.WriteBeginTag ("input"); writer.WriteAttribute ("type", "text"); writer.WriteAttribute ("name", UniqueID); if (ID != null) writer.WriteAttribute ("id", ClientID); writer.WriteAttribute ("value", Count.ToString ()); writer.WriteAttribute ("size", "3"); writer.Write (HtmlTextWriter.TagRightChar); writer.Write (" "); // Output another <a> tag writer.WriteBeginTag ("a"); writer.WriteAttribute ("href", "javascript:" + Page.GetPostBackEventReference (this, "inc")); writer.Write (HtmlTextWriter.TagRightChar); // Output a greater-than sign writer.Write ("&gt;"); // Output a </a> tag writer.WriteEndTag ("a"); } } }

Figure 8-16

AutoCounter control.

As usual, you need to compile AutoCounter.cs and place the resulting DLL in the bin directory before you run it. Then use AutoCounterPage.aspx (Figure 8-17) to take it for a test drive. AutoCounterPage.aspx responds to Increment, Decrement, and CountChanged events by displaying descriptive text at the bottom of the page. Click the < and > buttons a time or two to see what I mean. Then type a number into the control s text box and click Submit. The value that you entered should be echoed to the page.

AutoCounterPage.aspx

<%@ Register TagPrefix="win" Namespace="Wintellect" Assembly="AutoCounterControl" %> <html> <body> <h1>AutoCounter Demo</h1> <hr> <form runat="server"> <win:AutoCounter  Count="5" OnDecrement="OnDecrement" OnIncrement="OnIncrement" OnCountChanged="OnCountChanged" RunAt="server" /> <br><br> <asp:Button Text="Submit" RunAt="server" /> </form>

 <hr> <asp:Label  RunAt="server" /> </body> </html> <script language="c#" runat="server"> void Page_Load (Object sender, EventArgs e) { Output.Text = ""; } void OnDecrement (Object sender, EventArgs e) { Output.Text = "Count decremented to " + MyCounter.Count; } void OnIncrement (Object sender, EventArgs e) { Output.Text = "Count incremented to " + MyCounter.Count; } void OnCountChanged (Object sender, EventArgs e) { Output.Text = "Count changed to " + MyCounter.Count; } </script>

Figure 8-17

AutoCounter test page.



Programming Microsoft  .NET
Applied MicrosoftNET Framework Programming in Microsoft Visual BasicNET
ISBN: B000MUD834
EAN: N/A
Year: 2002
Pages: 101

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net