CoreCoder
LANGUAGES: C#
TECHNOLOGIES: Input Controls | Postback | IPostBackDataHandler | State Change
Detect Control Changes
Use server-side events to detect state changes.
By Dino Esposito
ASP.NET list controls, such as CheckBoxList and RadioButtonList, represent an effective way to group together multiple data items and choices. Typically, the user selects one element or more and posts back. On the server, you easily can retrieve the checked items and proceed with further processing.
In many real-world cases, though, this information alone is not enough. You also might need to know what items have changed since last time and whether any selections have changed. For example, suppose you have a form with an employee's personal data. The form contains a checkbox list in which each element represents a possible skill of that employee. Each time the form is processed and updated, you need to know which skills are selected at the time. More importantly, you want to know if any skills have been added or removed since the last time the form was posted.
Detecting selection changes in ASP.NET list controls is only one example of a more general problem: detecting property changes in input controls, which are hosted by HTML forms. As such, the ability to detect property changes is important for all input controls, not only list controls.
Initiate Postback Events
A handful of Web and HTML input controls fire server-side events whenever their internal status changes. The way these events are named is not consistent, but the feature is common to HTML input controls as well as list and Web controls. Input controls lack the ability to initiate postback events to let server-side code run. Typically, postback events are generated only by button and link controls in addition to all controls that support the AutoPostback property.
Many input controls support the AutoPostback Boolean property, but this property rarely is effective because it posts to the server every time the selection changes. Normally, that would be too often. What you really need is a way to submit changes to the server independent from clicking on the input controls. Once on the server, though, you should be able to process changes to the controls only if the user actually modified them since last time. For example, you might want to process the record associated with a selected checkbox only if the selection took place during the last round trip. For this to happen, you need a double level of events. First, the postback event moves you to the server. Second, the property-change event gives you a chance to process changes on the server.
Input controls expose a server-change event, but the code executes on the server only with the next postback - whichever generates it and whenever it occurs. For example, the TextBox control raises a TextChanged event when the content of the buffer changes between two consecutive posts to the server.
How Controls Handle State Changes
Consider the TextBox control and what happens when the page containing the control posts back. The page author defines the control like this:
OnTextChanged ="NotesChanged" /> The user types some text and submits the page. On the
server, the ASP.NET runtime restores the original state of the page, then enters
into a phase named Raise ChangedEvents. At this stage, each page control that
implements the IPostBackDataHandler interface is given a chance to update its
current state with data posted by the client. The ASP.NET runtime invokes
LoadPostData - one of the two methods defined by the IPostBackDataHandler
interface - sequentially on each control that implements this interface: public bool LoadPostData( string postDataKey, NameValueCollection postCollection); The first argument of LoadPostData is a string that
identifies the control; the second is a name-value collection that contains the
posted data. You use the first string as a key to access the posted value
stored in the collection. The posted value refers to one particular property on
the control - the one you would have retrieved by using Request.Form in old ASP
applications. The value is Text for textboxes and Checked for checkboxes. Controls that implement the IPostBackDataHandler interface
use a piece of boilerplate code to implement the LoadPostData method.
Basically, the method updates the key property of the control with the posted
value. The pseudo-code in Figure 1 shows how LoadPostData works for the TextBox
control. private virtual bool System.Web.UI.IPostBackDataHandler.LoadPostData( string postDataKey,
NameValueCollection postColl) { string oldValue; string postedValue; // Restored from view
state (must be enabled) oldValue = this.Text; postedValue =
postColl[postDataKey]; if (oldValue !=
postedValue) { this.Text = postedValue; return true; } return false; } Figure 1. The LoadPostData method for the TextBox
control updates the Text property with the posted value. LoadPostData returns True if the state of the control
changes as a result of the postback - that is, if the user generated a new
state for the control. For this infrastructure to work, the value of the server
control's UniqueID property must be assigned to the HTML element's Name
attribute. Otherwise, the ASP.NET runtime will not be able to handle postback
data for that control. The ASP.NET runtime tracks all the controls that return
True to LoadPostData and invokes the RaisePostDataChangedEvent method for each
of them. This code snippet reports what that method does for the TextBox
control: // System.Web.UI.IPostBackDataHandler void RaisePostDataChangedEvent() { this.OnTextChanged(EventArgs.Empty); } As you can see, the TextBox control simply executes the
user-defined server-side handler for the TextChanged event. In light of the
control declaration shown previously, the NotesChanged method executes: void NotesChanged(object sender, EventArgs e) { Trace.Warn("Notes field has been changed"); } If the page contains multiple input controls, each with a
state-change handler, the order of the controls in the page determines the
order in which handlers actually execute. All state-change handlers are
processed before the postback handler code runs. Figure 2 demonstrates this. Notice that state-change notifications are raised after
the controls' state has been restored from view state and finally updated using
posted data. Explore State-Change Events In Figure 3, you can see the list of all server controls
that implement the IPostBackDataHandler interface. The second column of the
table shows the property monitored; the third column details the corresponding
state-change event. Class Property State-Change
Event CheckBox Checked CheckedChanged CheckBoxList SelectedIndex SelectedIndexChanged DropDownList Selected SelectedIndexChanged HtmlInputCheckBox Checked ServerChange HtmlInputHidden Value ServerChange HtmlInputRadioButton Checked ServerChange HtmlInputText Value ServerChange HtmlSelect SelectedIndex
ServerChange HtmlTextArea Value ServerChange ListBox SelectedIndex ServerChange RadioButtonList SelectedIndex SelectedIndexChanged TextBox Text TextChanged Figure 3. These server controls implement the
IPostBackDataHandler interface. Three additional controls, not listed in Figure 3,
implement the IPostBackDataHandler interface: HtmlInputFile, HtmlInputImage,
and ImageButton. These all provide a significant implementation only for the
LoadPostData method. Their implementation of the RaisePostDataChangedEvent
method is void. The reason is that the controls feature properties to track
across page requests but have no architectural need to signal a state change.
The HtmlSelect control can work either as a dropdown list or list box depending
on the settings you define. In the case of a dropdown list, it monitors the
SelectedIndex property for changes; in the case of a list box, the control
detects changes in the Items collection. Each item in the collection is
represented by a ListItem object. The HtmlSelect control looks after the
Selected property of the various ListItem elements. Notice that, for the
state-change events to work correctly, the controls must have the view-state
feature enabled. If the view state is disabled, there is no way to compare the
posted value with the previous value of the control. No exception is ever
raised, but the result you get is unreliable because the posted value always is
compared with the default value of the monitored property rather than with the
value held when the previous post occurred. Put it All Together The page shown in Figure 4 contains a few input controls,
each with a registered state-change event handler. The code in the event
signals that the value in the control has changed by turning the background
color to yellow. What you need to do in response to a state-change event
depends in particular on the nature of the application, but this code provides
a starting point: public void TextChanged(object sender, EventArgs e) { ((WebControl)
sender).BackColor = Color.Yellow; } public void NotesChanged(object sender, EventArgs e) { HtmlTextArea area =
((HtmlTextArea) sender); area.Style["background-color"] = "yellow"; } public void IndexChanged(object sender, EventArgs e) { ((WebControl)
sender).BackColor = Color.Yellow; } The background color then is
restored to the default color in the Page_Load event. The events are raised only if the monitored property
changes during the client activity. When the page posts back, the comparison is
done between the value stored in the view state and the current value posted by
the client. Notice that when these state-change events occur, the programmer
receives no additional information about what has changed. The programmer is
notified only that the value for the monitored property has changed. This is
not an issue for standalone controls such as the TextBox, but it is an issue
for list controls such as CheckBoxList and RadioButtonList. In these cases, you
simply don't know what items have changed since last time. A possible
workaround would be to store the indexes of the checked items in a page-specific
entry in the view state and compare it with a list of checked items within the
SelectedIndexChanged event handler. Alternately, as long as the application
logic allows it, you could disable the checked items so that they are the only
items changed at each successive postback. This approach works, however, only
in situations in which you use the checkbox list to flag out items. Detecting state changes on the server, in the context of a
postback event, is a critical programming aspect in several applications. For
example, for applications that need to accomplish heavy updating tasks, knowing
whether a given field has been updated since the last round trip can be a
significant optimization. The state-change events don't initiate a postback
themselves, but if the controls have the AutoPostback property set to True,
each state change posts the page back automatically. Not all controls expose
the AutoPostback property; it is a specific feature of list controls. You set
it to True only if the server needs to capture the selection as soon as it is
made to populate other controls. The sample code in this
article is available for download. Dino Esposito is a trainer and consultant who
specializes in ASP.NET, ADO.NET, and XML. Author of Building Web Solutions with ASP.NET and
ADO.NET and the upcoming Programming ASP.NET (Microsoft Press), he also is a co-founder
of http://www.VB2TheMax.com.
E-mail him at mailto:[email protected]. Tell us what you think! Please send any comments about
this article to [email protected].
Please include the article title and author.
Figure 2. State-change events are processed before the postback event
code executes.
Figure 4. In this page, you know the values of the input controls are
changed because the code turns the background color of the fields to yellow.