asp:Feature
LANGUAGES: VB.NET
ASP.NET VERSIONS: 2.0
Waste Management
Improving Web Application Performance Using Client Callbacks
By John Paul Mueller
One of the biggest time wasters for current Web applications is that they usually send an entire Web page to the server for processing. In many cases, the entire refresh process affects a single control on the Web page. Consequently, refreshing the entire Web page wastes network bandwidth, server processing time, and even some client time. Developers often overcome the problem of having to send the page back to the server by performing processing locally using client-side scripting. However, this solution is limited because of client resources and the need to access server-side resources.
Fortunately, ASP.NET 2.0 provides an alternative in the form of client callbacks. A client callback replaces the information on only part of the Web page, rather than sending the entire page back for processing. In fact, the only information that goes back to the server is the information required to make a request. The only information the server returns is the data the client requires to display information on screen. Client-side processing manages the sending and receiving of data, so what you actually get is an extremely efficient data exchange that combines the best features of server-side processing with client-side scripts.
You can use client callbacks for any kind of data. For example, if you want, you can create a scenario where the Web page displays the current server time and automatically updates it once a second. Another example would be accessing a middle tier component that computes a quote for services based on the user s input and displaying the output on the Web page. However, this article discusses a more common example: database management. To keep things simple, I chose to use the Suppliers table from the Northwind database. The user will select one of the suppliers in the database and see the associated data, such as address, on screen. The main difference between this example and any other database example you might have seen in the past is that it never sends the entire page back to the server. Every new record is an update of the requisite controls on the existing Web page; only the required information passes between client and server.
Sending the Request
This example uses a few tricks you can use with other controls in many other situations. The first trick is to provide a means for making an ASP.NET control call a local script when it apparently doesn t support this feature. The example fills a DropDownList control with the list of company names. To perform this task, you must use the ASP.NET control, rather than an HTML control that lets you call a local script. Interestingly enough, Microsoft assumes you ll always want to use code-behind with the DropDownList control, but you can easily override that assumption by creating this control, as shown in Figure 1. Notice the onchange attribute. Because the DropDownList control doesn t actually support this attribute, Visual Studio will complain. However, when you compile and run the code, you ll find that the onchange attribute does work with the resulting
onchange= SendRequest runat= server EnableViewState= False >
Figure 1: Adding local script support to a control.
The Web page code doesn t actually contain a SendRequest script, however. You use script injection techniques to create this script as part of the Page_Load event handler, as shown in Figure 2.
Protected Sub Page_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles Me.Load
Create the script.
Dim CallBackScript As String
CallBackScript = _
function SendRequest _
+ vbCrLf + { + vbCrLf + _
var Input = form1.ddlCompanyName.value + vbCrLf + _
var Context = + vbCrLf + vbCrLf + _
Page.ClientScript.GetCallbackEventReference( _
Me, _
Input , _
ShowData , _
Context , _
ShowError , _
False) _
+ ; + vbCrLf + } + vbCrLf
Inject the script onto the Web page.
Page.ClientScript.RegisterClientScriptBlock( _
Me.GetType, SendData , CallBackScript, True)
Obtain the connection string.
Dim CTS As ConnectionStringSettings = _
ConfigurationManager.ConnectionStrings( _
NorthwindConnectionString )
Obtain a list of companies.
Dim ConnSupplier As New SqlDataSource( _
CTS.ConnectionString, _
SELECT [CompanyName] FROM [Suppliers] )
Dim Suppliers As DataView = _
ConnSupplier.Select(DataSourceSelectArguments.Empty)
Place the company names in ddlCompanyName.
For Each Supplier As DataRowView In Suppliers
ddlCompanyName.Items.Add(Supplier(0).ToString)
Next
End Sub
Figure 2: Creating the SendRequest script and filling the list with data.
The code creates a string that contains the script. This script begins with two statements that define two values, the input value and the context. In most cases, you don t need the context, but it can become helpful when you need to consider the user s environment or other factors. The input value, Input, contains the current selection from ddlCompanyName and passes it along to the server.
The script must also call the WebForm_DoCallback method. However, you don t enter this information directly. Instead, you use the Page.ClientScript.GetCallbackEventReference method to obtain a specially formatted string from ASP.NET. Using this approach ensures that ASP.NET creates all required automation code for you. The GetCallbackEventReference requires several inputs, including:
- A reference to the control that handles the callback.
- The name of the Web page variable that holds the input data.
- The name of a function on the Web page that the callback will call when the call succeeds.
- A variable containing context information for the callback.
- The name of a function on the Web page that the callback will call when the call fails.
- A Boolean value that determines whether this is an asynchronous call.
Once you create the script as a rather long string, you can inject it into the Web page using the Page.ClientScript.RegisterClientScriptBlock method. This method requires that you provide the script type, the key used to identify the script, the string containing the script, and a Boolean value that determines whether the resulting script contains script tags. It s actually easier to see the result of all this work by viewing the script result in the browser. Right click the browser and choose View Source from the context menu. Figure 3 shows how the script will appear to the browser.
Figure 3: Use the browser to view
the results of injecting the script.
This example also shows an interesting way to obtain data from the Suppliers table of the Northwind database. The code begins by obtaining the connection string information from the web.config file. You can easily create this connection string by dragging and dropping a SqlDataSource control onto the form. After you configure the data source, delete the control (you don t actually need it to make the example run). The only reason to use this approach is to let the IDE automation work to your advantage. Now you have a connection string that you can access without having to write all the required code by hand.
The ConfigurationManager.ConnectionStrings method obtains the connection string information that you created in the web.config file and places it in CTS. You can then use the CTS.ConnectionString property to create a new SqlDataSource object, named ConnSupplier. In addition to the connection string, you need to provide a SQL query to retrieve the data. In this case, all we need is the CompanyName field values from the Suppliers table.
The code uses the ConnSupplier.Select method to retrieve the data values from the database. DataSourceSelectArguments.Empty tells ASP.NET that you want all the entries. You could define paged output by creating a DataSourceSelectArguments object and supplying the required arguments. A For Each...Next loop completes the task of adding all the company names to ddlCompanyName so the user can select a particular company from the list.
Processing the Request
The client request (a company name) ends up at the server every time the user chooses a new company. ASP.NET directs this data to the RaiseCallbackEvent method, as shown in Figure 4.
Public Sub RaiseCallbackEvent(ByVal eventArgument As String) _
Implements ICallbackEventHandler.RaiseCallbackEvent
Obtain the connection string.
Dim CTS As ConnectionStringSettings = _
ConfigurationManager.ConnectionStrings( _
NorthwindConnectionString )
Modify the event argument as needed to compensate for
single quotes.
If eventArgument.Contains( ) Then
eventArgument = eventArgument.Replace( , )
End If
Obtain a list of companies.
Dim ConnSupplier As New SqlDataSource( _
CTS.ConnectionString, _
SELECT * FROM [Suppliers] + _
WHERE [CompanyName]= + eventArgument + )
Dim Suppliers As DataView = _
ConnSupplier.Select(DataSourceSelectArguments.Empty)
Get the only row the result should contain.
Dim Supplier As DataRowView = Suppliers(0)
Combine the results into a single string.
Output = New StringBuilder
Output.Append(Supplier(0).ToString + ; )
Output.Append(Supplier(2).ToString + ; )
... Other Appended Data ...
Output.Append(Supplier(11).ToString)
End Sub
Figure 4: Processing the user request.
The event handler accepts just one input: a string containing the event argument, which can contain anything as long as you use a string. The code in this section should look familiar; it s a different form of the database code used to obtain a list of company names. Essentially, this code works the same, but you should note a few differences.
One of the most important differences is that the code must check for single quotes. Some of the supplier names contain single quotes and the code will fail unless you change these single quotes to single quote pairs, as shown in the code. Use the eventArgument.Contains method to detect the single quotes. When the code detects a single quote, it performs a replacement using the eventArgument.Replace method. Notice that the eventArgument value appears as part of the WHERE clause for the SQL statement that selects a single record from the database to use for the response.
The code must still place the resulting row into a DataRowView variable named Supplier. At this point, the code can access each of the individual field values using an index into Supplier. The code relies on a global variable, Output, to hold the data. This StringBuilder object contains a single string that separates each of the data fields using a semicolon.
Receiving the Data
At this point, the example has received the request and processed it. The data appears in the Output variable. Now it s time to send the data back to the client. The process begins with the GetCallbackResult event handler shown in Figure 5. All that this event handler has to do in this case is convert the StringBuilder into a string and return it to the caller. However, this method can perform any level of post processing you require to massage the data before the client sees it.
Public Function GetCallbackResult As String _
Implements ICallbackEventHandler.GetCallbackResult
Send the result to the Web page.
Return Output.ToString
End Function
Figure 5: Outputting the data from the server.
Every client request has two possible outcomes: the call will either succeed or fail. When the call fails, the best you can do is display the error information to the user. The example uses the ShowError function, shown in Figure 6, for this purpose. With good error trapping, you can usually provide a clear and easily understood message. However, this example demonstrates a technique for ensuring a minimum of errors. The only input the user has is a dropdown listbox that contains entries you retrieved from the database. The user can enter any new data, there isn t any posted data to worry about, and all the other data fields on the Web form are read-only. When a call succeeds, the example uses the ShowData function shown in Figure 6 to display the data on screen. Every application that relies on client callback must have these two functions.
Figure 6: Accessing the response in the client.
Processing the data from the server is relatively easy. All you need to do is use the Output.split function to create an array of parsed values. Each array element contains a single data field, so you set the value of that data field to the array value. You won t find anything too fancy here. Figure 7 shows typical results for this example.
Figure 7: Displaying the result of a
user selection on screen is relatively easy.
At this point, you can see that you can retrieve any record from the database without changing the Web page at all. In fact, this example only requires two relatively short strings for the exchange, so the resulting network traffic is minimal. In addition, by combining the client-side and server-side processing, you can obtain the best of both worlds.
An Additional Benefit
Admittedly, this technique is quite fast and efficient. However, you can configure your Web application to make the application even more efficient. Because the user will never send the Web page back to the server, you don t need some of the overhead that ASP.NET normally requires. For example, you can set the EnableViewState property of every control to False. Offering additional performance gains without any loss of functionality, this change alone will reduce the size of the Web page considerably. Likewise, you can set the EnableSessionState property for each document to False to obtain similar performance-boosting results.
It s important to approach each of these changes with care. Make sure you test the resulting pages for every possible data entry scenario to ensure you don t require any of the standard ASP.NET state support. Generally, you ll find that everything works fine as long as you rely exclusively on client callback to perform data updates.
The source code accompanying this article is available for download.
John Mueller is a freelance author and technical editor. He has writing in his blood, having produced 69 books and more than 300 articles. The topics range from networking to artificial intelligence, from database management to heads-down programming. His most recent book is Web Development with Microsoft Visual Studio 2005 (Sybex, ISBN: 078214439X). His technical editing skills have helped more than 48 authors refine the content of their manuscripts. You can reach John via e-mail at mailto:[email protected], or visit his Web site, http://www.mwt.net/~jmueller/.