nowing whether a group of form fields have changed and which fields have changed can be beneficial in many ASP.NET Web applications. ASP.NET triggers a “value change” event at the server side for each server control for which the client changed the value upon postback.
Many applications contain groups of related form fields, often correlated with each other and typically corresponding to member data in a business object. Thus developers often need to check if such a group of form fields has changed as a whole.
Unfortunately, the .NET Framework (1.x, 2.0) doesn’t offer an effective solution. This article will present an elegant technique to solve this problem and it can even go further so that you can know which field has changed. Let me list a few of the benefits of this technique:
- Provide a reliable server-side method to detect changes on form fields as a whole, letting you persist only changed groups of form data into the database, thus avoiding unnecessary database update via stored procedures or embedded SQL. In the worst case, the technique imposes a marginal overhead due to change-checking logic.
- You can build embedded UPDATE SQL statements on the fly to update only changed columns when form field values change and form data corresponds to columns in a table. Thus, this technique can reduce coding and possibly improve performance.
- You can update multiple table records or groups of form data in batches with one click of a button. Because this technique avoids unnecessary database updates, it doesn’t degrade overall performance. More significantly, you can reduce page postbacks. In contrast, ASP.NET 2.0’s support out of the box either updates all records with one button click or associates an update button with each record (typically seen in an iterative control such as Repeater or DataList, and so on).
Installing and Compiling the Sample Code
I based the code in this article on the .NET Framework 1.1 and Visual Basic .NET; however you can easily port the code to either the .NET Framework 1.0 or 2.0 by recompiling it. The .zip file contains two projects: The xControls project implements the logic to detect form data changes, and the DetectFormValueChange is a small Web project for demo purposes. Copy the code into a virtual directory named DetectFormValueChange and browse to the TestChange.aspx Web Form to run the demo.
Developers might try to hook the value-change event handler for each server control, but that leads to tedious, messy code, and worse?code that’s not reusable. To solve this problem, you’ll use a technique called subclassing that extends the functionalities of server controls by supplying an interface, hooking up an event handler, etc. The resulting subclassed code is well-encapsulated and offers good reusability. You’ll use three straightforward steps to implement this server-side technique.
- Identify server controls that can hold form data and extend them with a common interface property called FormGroup that lets you distinguish different groups of form fields.
- Hook up the value change event handler to save form data change information. When a form field value changes, this special handler executes and stores the change information for use in a later database query.
- Execute implementation logic to determine how change information of the form fields is stored and queried. The logic exists in static functions and is encapsulated in a separate class.
Step 1: Identify Server Controls and Extend Common Interface
Identify all the server controls that present data for users to edit, including TextBox, DropDownList, RadioButton, CheckBox, ListBox, RadioButtonList, CheckButtonList, etc. You should also include Literal and Label controls because developers may use them to display immutable fields such as table primary keys. You could expand this list even further. For example, ASP.NET 2.0 comes with a server control called HiddenField that you might include. Developers can also include customized controls in this list by following the changes below. After identifying the server controls, you need to extend them by implementing the following interface:
Public Interface IFormGroup Property FormGroup() As String End Interface
IFormGroup supplies the controls with a common interface (other than Control) and serves to single out grouped fields within a container easily. The FormGroup property could be of type integer, but to make it more generic I made it a string. A FormGroup typically corresponds to a key column of a table record. The FormGroup value should be unique for different groups of form data. You implement the FormGroup property following the same convention as other server control properties.
Public Property FormGroup() As String Implements IFormGroup.FormGroup Get Dim obj As Object obj = ViewState("FormGroup") If obj Is Nothing Then Return Nothing Else Return CType(obj, String) End If End Get Set(ByVal Value As String) ViewState("FormGroup") = Value End Set End Property
Step 2: Hook up the Value Change Event Handler
When a form field changes, the postback value change event handler, if any, will be triggered. You should exclude Literal and Label because these two controls present data in read-only fashion. The event name and argument might be different for different controls, but the body of the event handler remains unchanged; it just calls a static function called AddChangedField. AddChangedField is a support function responsible for recording the value change information of a control associated with a specific FormGroup value. I’ll provide more detail on the support functions in the next step. For simplicity I’ll use a TextBox control as an example.
Private Sub ValueChanged(ByVal sender As Object, _ ByVal args As EventArgs) Handles _ MyBase.TextChanged xControls.FormGroup.AddChangedField(FormGroup, ID) End Sub
Step 3: Implement Logic to Store and Query Change Information of Form Data
A number of functions work together to realize this technique, all of which are static, so you can bundle them into a separate FormGroup class, as seen in Listing 1. The AddChangedField method records field changes for its associated FormGroup. Note that if the value of FormGroup is missing (e.g. Nothing in VB.NET), the change information will get lost because each form field must belong to a group. Other similar methods include:
- HasFormGroupChanged?Checks whether any control in a form group has changed.
- HasFormGroupFieldChanged?Checks if a specific form field in a form group has changed.
- FindFormGroupFields?An overloaded function that finds all server controls within a container that belong to the same form group.
- FindChangedFormGroupFields?Singles out all changed fields in a group of form fields.
So where does the changed information get stored? A NameValueCollection instance records changes of all fields centrally in the global cache HttpContext.Items. HttpContext.Items is a page-level cache and accessible to all server controls. The key called $#!@FormGroup is special and unique and should not be overridden. Each server control has a unique key as well, computed by the BuildControlKey method based on a control’s FormGroup and the control’s ID.
A Couple of Demonstrations
To illustrate this technique I’ll create a simple Web Form called TestChange.aspx. Users can edit this form and then submit form data. The page will then display the IDs of all changed form fields, as shown in Figure 1. A Panel control serves as the container of all form fields. The code calls the FindChangedFormGroupFields in the Submit button’s Click event to extract the IDs of the changed form fields.
If you change some fields and submit the form, it will display the IDs of changed fields in red. After you click Submit, nothing is immediately shown since no change takes place at this time. In the subsequent two sections I will provide pseudocode for two sample applications. One will update form data upon change. The other will build a SQL UPDATE statement on the fly. You can easily apply the skills I present in this article to enterprise Web applications to increase performance and enrich the flow of your Web sites.
|Figure 1: This simple Web page vividly reveals how a change of form field is detected.|
Application 1: Update Changed Form Data
Consider an iterative control such as a Repeater that contains multiple groups of form fields; each RepeaterItem serves as the container for one group of form fields. You bind a FormGroup to the first server control within a repeater item (which might be an invisible Literal if your user does not need to see it). Listing 2 shows how to iterate through a Repeater to update a changed group of form fields within RepeaterItems.
Application 2: Build a Dynamic SQL UPDATE Statement
If a group of form fields corresponds to a data table record, you can build a dynamic SQL UPDATE statement based on change information from the data fields. To make this work, you need to know the name, the SQL type, and the value for each query parameter. For simplicity, assume the control ID for each control is the same as the parameter name. Next, add two other properties, SqlType and SqlValue, to the common interface to supply the SQL parameter type and value. The code below shows how to build a SQL UPDATE statement using SqlParameter. You can discover the parameter names and types via reflection, but you cannot build a dynamic SQL UPDATE statement without knowing which field(s) have changed.
'Build Sql update statement according to table name, and column 'names (changed form fields) ''' ''' UPDATE tableName SET column1=@column1, column2=@column2 ''' WHERE keyColumn1=@keyColumn1 AND keyColumn2=@keyColumn2 ''' 'Build Sql Parameter, suppose ifg is type of IFormGroup ''' ''' SqlParameter aParam= new SqlParameter("@" & columnName, ''' ifg.SqlTtype) ''' aParam.Value = ifg.SqlValue '''
Some developers may argue that embedded SQL may hurt performance compared with stored procedures. Updating all columns of a table record using embedded SQL or stored procedures may incur an unnecessary database retrieval to restore state upon page postback, and updating many columns using stored procedure may also decrease performance compared to updating only few changed columns using a dynamically-built embedded SQL UPDATE statement. Without a doubt, dynamically built embedded SQL will improve performance if you only use embedded SQL. You must choose whether to use embedded SQL or stored procedures.
This article presents a reliable and effective server-side technique to detect changes in form fields as a whole, which can help to avoid unnecessary data access, reduce coding, and enrich the user experience. I hope you enjoy the technique and use it in your applications. If you want to use it in ASP.NET 2.0, simply recompile the code in Visual Studio 2005.