Windows DevCenter    
 Published on Windows DevCenter (http://www.windowsdevcenter.com/)
 See this if you're having trouble printing code examples


The Three Faces of ASP.NET AJAX

by Jesse Liberty, Dan Hurwitz
02/27/2007

The new Microsoft ASP.NET AJAX web development framework (which we'll refer to simply as AJAX throughout the rest of this article) is like a mythological figure with three faces to its personality: one is friendly but rigid, one is playful, and the third is, to most ASP.NET developers, a bit frightening.

Face number one is the friendly ASP.NET-like face of AJAX that lets you drag and drop AJAX controls onto your forms just as you would standard ASP.NET server controls, but gives you client-side, wiz-bang performance without having to understand a thing about JavaScript, the Document Object Model, DHTML, or Asynchronous communication with the host. Cool, eh?  I love that face of AJAX, and I deeply resent that this wonderful, lovely, beautiful ASP.NET-like face is often the last one shown to ASP.NET programmers, which I why I wrote a rant on the subject last summer.

That said, there are times that you want to go beyond the controls AJAX provides you, whether server-side or client-side. Server-side, you might want to create a custom-control. Client-side, you might want to create your own AJAX extender to modify the behavior of an AJAX client-side control; that is the second face of AJAX. Microsoft anticipates this, and provides help for creating your own custom AJAX extenders.

Finally, sometimes (and far less often than some would have you believe) you may be forced to set aside all maps and guidebooks and bravely go where none have gone before. If you are a JavaScript and DHTML programmer, this is a piece of cake. It's what you do every day, and you'll laugh (laugh, I say!) at how trivial it is.

If, like us, however, you've studiously avoided writing JavaScript (ugly, untyped, nasty little language that looks like C but will bite you the first chance it gets), then you'll want to draw a careful pentangle on the floor, light the candles in just the right order, say the ancient incantations in Aramaic, and follow along closely as we reveal the third (rather horrifying) face of AJAX: the underlying gibberish that makes it all work.

Getting Set Up

To work with any of these characters, you'll need to set up AJAX for your development environment. Fortunately, this is now incredibly easy (and safe) to do. First, buy a computer. Second, install Visual Studio 2005. (You can also use Visual Web Developer, a free download, except you will not be able to create your own AJAX control extenders, which is the second face described in this article.)

Third, and finally, download and install the AJAX software. Go to the AJAX site (http://ajax.asp.net/) and complete the following steps:

  1. Download the ASP.NET 2.0 AJAX Extensions 1.0 installation file. This is a file called ASPAJAXExtSetup.msi. Save it to a convenient folder on your system, then double click on the file in Windows Explorer to start the installation process. Follow the wizard.
  2. Download the AJAX Control Toolkit and save it to a convenient folder on your machine. This will be a zipped archive file. There are two versions: AjaxControlToolkit.zip and AjaxControlToolkit-NoSource.zip. The first contains the source code for the components it contains, while the second contains no source code. You are a programmer. You should know which one you want.
  3. Unpack the AJAX Control Toolkit archive folder into a convenient folder on your computer
  4. Create a new website from the ASP.NET AJAX website template by opening the File menu, clicking New Web Site..., and picking ASP.NET AJAX-Enabled Web Site under "Visual Studio installed templates." (Yes, we know this is a strange way to install software, but we didn't make it up; the people at Microsoft did, and it is their toy.)
  5. Right-click on the Toolbox, select Add Tab, and name your new tab "AJAX Control Toolkit."
  6. Right-click inside that tab and select Choose Items....
  7. When the Choose Toolbox Items dialog appears, click the Browse... button. Navigate to the folder where you unzipped the ASP.NET AJAX Control Toolkit package. You will find a folder named SampleWebSite, and under that another folder named Bin. Inside that folder, select AJAXControlToolkit.dll and click Open. Click OK to close the Choose Toolbox Items dialog.
  8. You can now use the included sample controls in any of your websites.

If you are using VS2005, and you want to create your own control extenders, complete the following additional steps:

  1. In the folder where you unzipped the AJAX Control Toolkit archive, you will find a folder called AjaxControlExtender. Inside that folder is a file called AjaxControlExtende.vsi. Double-click that file to install it.
  2. Choose which templates you want to install (all), then click Next. Then click Yes to allow unsigned content, then Finish and Close.

Friendly Drag-and-Drop AJAX

The friendly face of AJAX is drag and drop, which lets you place client-side controls on your web pages just as ASP.NET lets you do with its server controls. Begin by opening Visual Studio (or VWD) and choosing Create  Web Site  . From the templates, choose ASP.NET AJAX-Enabled Web Site. Pick your language of choice (Visual Basic for this article) and a location on your hard drive; name your new application DragAndDropAjax, and click OK.

To get started, you'll create a simple application with a text box, into which an administrator may type a user's name and a Delete button (initially disabled). When the administrator enters a name into the text box, the Delete button will be enabled, and when the Delete button is pressed, the username will be deleted from the membership database (we'll fake that last part!).

Script Manager

Visual Studio (or VWD) will set up your environment to support an AJAX-enabled application. This includes adding an object of type ScriptManager to your default.aspx file, as shown in Figure 1.

Script Manager
Figure 1. Script Manager in boilerplate markup (Click to enlarge.)

The job of the Script Manager is to coordinate the action of all the AJAX controls on the page. Now drag and drop the following controls from the toolbox onto the page.

Control ID Text
Label Label1 Name:
TextBox Name  
Button Delete Delete

Set the Enabled property of the Button to false.

If this were a real production app, it would be much better to validate that the name entered corresponds to a real user on the system who is eligible for deletion. However, to keep the example simple (and because we are incredibly lazy), we will leave that as an exercise for the reader.

Source view will look something like Figure 2.

TextBox & Save in Design view
Figure 2. TextBox & Save Button in Design view (Click to enlarge.)

You want to ensure that when any text is entered, the Delete button is enabled. To do so, you'll implement an event handler for the TextChanged event of the text box. TextChanged is the default event for a TextBox, so you can quickly create this event handler by switching to Design view and double-clicking on the TextBox. The IDE will create an event handler for you named for the control (Name) and the event (TextChanged), and place you inside the Name_TextChanged event handler, ready to enter the following highlighted line of code:


Protected Sub Name_TextChanged( _
ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles Name.TextChanged

   Delete.Enabled = Name.Text.Length > 0

End Sub

As you can see, whenever any text is entered in the Name text box, the button is enabled; otherwise, it is disabled.

Our next goal is to handle the event fired when the administrator clicks the Delete button. Normally, we'd check the database for the name, and then delete that user from the database (perhaps after receiving confirmation). For now, we'll just set the text box field to empty and the button back to disabled.

The default event handler for the button is Click, so you can just double-click on the button, which will create the event handler. Enter the highlighted code below:

Protected Sub Delete_Click( _
ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Delete.Click
   Name.Text = String.Empty
   Delete.Enabled = False
End Sub

Run the application and type a name in the text box. Unfortunately, the button never becomes enabled. The text box does not automatically post back after each keystroke (and a good thing, because it would cause enormous inconvenience!). We can fix that by switching to Design view, clicking on the text box, and then in the Properties window, setting the text box's AutoPostBack property to True. Now, run the application again. Type some text, and then tab off of the text box (forcing the post back to the server). Aha! The page posts back and the button becomes enabled!

If you click the button, the page posts back again, the text box is cleared, and the button becomes disabled.
It may be difficult to see the disadvantage of posting back the entire page in this small example, but normally there are many other controls on a page like this that do not need to be updated and that will flicker when your data is updated. To simulate this, let's temporarily add two other controls to the page so that we can see the effect of a postback on these controls. We'll add a list of checkboxes and a calendar control.

Make sure you are in Design view, and drag a CheckBoxList onto the page from the Toolbox. Use the Edit Items link on the smart tag to add some items to the list (see Figure 3 for some examples).  Next drag a Calendar control onto the page. The result should resemble Figure 3.

Adding items
Figure 3. Adding items to the page

Run the example again, and watch the new items flicker each time you post back the page! It is to avoid this flicker that we'll use the AJAX update panel.

Adding an Update Panel

In Design view, drag an UpdatePanel control from the AJAX Extensions section of the Toolbox onto the page, below all the other controls. Then drag all three of the original UI controls—the label, the text box and the button—directly into the UpdatePanel. It should look more or less like Figure 4.

UpdatePanel
Figure 4. Placing an UpdatePanel in Design view

In Design view, drag an UpdatePanel control from the AJAX Extensions section of the Toolbox onto the page, below all the other controls. Then drag all three of the original UI controls—the label, the text box, and the button—directly into the UpdatePanel. It should look more or less like Figure 4.

Switch to source view and find the markup for the Update panel (UpdatePanel1 in the following snippet).

asp:UpdatePanel ID="UpdatePanel1" runat="server">
     <ContentTemplate>
        <asp:Label ID="Label1" runat="server" 
                   Text="Name:" />
        <asp:TextBox ID="Name" runat="server" AutoPostBack="True" />
        <asp:Button ID="Delete" runat="server" 
                    Text="Delete"  Enabled="false" />
     </ContentTemplate>
</asp:UpdatePanel>

Other than adding a ContentTemplate for you (thanks!), the Label, TextBox, and Button controls are undisturbed by UpdatePanel. This is key: AJAX controls do not change the ASP.NET controls, they just extend their magic over existing ASP.NET controls.

Run the application. The Update Panel will handle the AutoPostBack, and rather than posting back the entire page, it will post back just the contents of the panel, asynchronously, sparing the rest of the page the postback and eliminating the flicker. Ahhh, so much better!

Feel free to delete the CheckBoxList and the Calendar control at this point, as they have served their purpose in illustrating the effects of the update panel.

Using an Extender: ConfirmButtonExtender

The AJAX controls are provided in two packages: the smaller set that is placed in the AJAX tab and the far larger set that you placed in the AJAX Control Toolkit tab that you created when you installed the toolkit.

Many of the tools in the AJAX Control Toolkit are not standalone controls, they are extenders; that is, they extend the capabilities of existing AJAX controls . To see this at work, create a new website named ToolkitExtenders that is an exact copy of the previous website.

We'll use an extender to make sure the administrator really, truly wants to delete a user. Rather than creating a special button that confirms the user's intent, or even adding code to the button's Click event, we'll add client-side code to confirm the user's decision by adding a ConfirmButtonExtender and telling it that its "target control" is our Delete button. This will create client-side code to check the decision without requiring a roundtrip to the server, thus creating a much faster and more responsive application.

In Source view, locate the Delete button within the UpdatePanel and drag a Confirm Button Extender from the AJAX Control Toolkit tab directly below the Delete button, taking care that the new control stays within the ContentTemplate element. Your new control will have the prefix CC1 (or AjaxToolkit, depending on how your system is configured). You'll note two other changes: a Register directive will have been placed at the top of your page, and, if you expand your bin directory, you'll see the .dll and .pdb files for the AJAX Control Toolkit. Handy.

While the ConfirmButtonExtender will have automatically been assigned an ID, you'll have to assign the TargetControlID property yourself, though Intellisense will help. You'll also want to set the text for the confirmation in the ConfirmText property, using the following code snippet:

<asp:Button ID="Delete" runat="server" 
   Text="Delete"  Enabled="false" />

<cc1:ConfirmButtonExtender 
   ID="ConfirmButtonExtender1" 
   runat="server"
   TargetControlID = "Delete"
   ConfirmText = "Really, truly delete?" />

When the administrator clicks the Delete button, the ConfirmButtonExtender will take control and cause a confirmation dialog to appear with your confirmation text, as shown in Figure 5.

False Triggers
Figure 5. Confirmation Extender (Click to enlarge.)

The Second Face: Customized AJAX

If the extenders that are provided with the toolkit do not meet your needs, you are free to create your own. AJAX will even help, and a walk through is provided, with the Toolkit from which we have borrowed liberally for this example (don't write in, we know we're not being original, but we hope we're adding clarity and context!)

To create your own AJAX Control Extenders, you must use Visual Studio 2005. Visual Web Developer (the free download) will not work.

One limitation of the previous examples is that the button does not become enabled until you actually leave the TextBox, at which point the TextChanged event fires. It would be better if you could detect keystrokes in the TextBox directly and enable the Button accordingly.

A custom AJAX Control Extender is just the ticket. To create one, copy the ToolkitExtenders website to a new website, called CustomExtender.

When you copy the website, the .dlls and Register directive may not copy correctly. If that happens, don't panic. Simply select the ConfirmButtonExtender and press Control-X to delete it. Drag a new ConfirmExtender into its place, which will cause it to be registered and cause the .dlls to be put in place. Then select the new one and hit Ctrl-V to overwrite it with the one you deleted, and Hey! Presto! You're back in business.

After you've created your copy, immediately right-click on the solution and choose Add New Project. Select ASP.NET AJAX Control Project from the My Templates section of the Add New Project dialog box. Name your new project ChangeAwareButton, as shown in Figure 6.

If you would rather work in a language other than Visual Basic, drill down through the corresponding project type from the dialog box.

False Triggers
Figure 6. Adding an AJAX Control Project to the website (Click to enlarge.)

This will add a project to the website, and that project will contain several autogenerated files: ChangeAwareButtonBehavior.js, ChangeAwareButtonDesigner.vb, and ChangeAwareButtonExtender.vb, as shown in the Solution Explorer shown in Figure 7.

Solution Explorer
Figure 7. Solution Explorer showing the AJAX Control Project

The JavaScript file is the one where you will be doing most of your coding. This file contains the script that runs on the client, implementing the logic and behavior (hence the name of the file) of the control.

The VB file ending with "Designer" contains a class that enables design time functionality and is not typically modified.

You will be coding in the other VB file, ChangeAwareButtonExtender.vb. This is the server-side class that allows an extender to be created. It also allows you to create and access properties of the control extender. Any properties created in this class must also have matching properties defined in the JavaScript Behavior file.

Implementing the Functionality

Open the Extender file, ChangeAwareButtonExtender.vb. You will see there are several attributes decorating the ChangeAwareButtonExtender class, including one called TargetControlType. In the boilerplate, the type of the target control is Control, meaning any type of control will qualify. Change this to TextBox to limit the target types to TextBoxes, as shown in the highlighted code in the following snippet:

<Designer(GetType(ChangeAwareButtonDesigner))> _
<ClientScriptResource("ChangeAwareButton.ChangeAwareButtonBehavior", _
    "ChangeAwareButton.ChangeAwareButtonBehavior.js")> _
<TargetControlType(GetType(TextBox))> _
Public Class ChangeAwareButtonExtender
    Inherits ExtenderControlBase

Rename the default property name MyProperty to something more meaningful, such as TargetButtonID. This must be changed in three places in the Extender file, ChangeAwareButtonExtender.vb, and in five places in the JavaScript Behavior file, ChangeAwareButtonBehavior.js.

When doing the search and replace, do not match whole words only, since you need to replace this._MyPropertyValue with this._TargetButtonIDValue.

Add a second property, ButtonText, that will allow you to dynamically set the text on the button. In order to add a property to an AJAX control extender, you must modify two files in synchrony: the JavaScript Behavior file and the VB Extender file.

First add the following public string property definition to ChangeAwareButtonExtender.vb:

<ExtenderControlProperty()> _
<DefaultValue("")> _
Public Property ButtonText() As String
    Get
        Return GetPropertyValue("ButtonText", "")
    End Get
    Set(ByVal value As String)
        SetPropertyValue("ButtonText", value)
    End Set
End Property

Even though VB is not normally case-sensitive, in this case the property name is case-sensitive because it must correspond exactly with the property name in the JavaScript file.

IDReferenceProperty

This extender will extend the text box, but it will reference the button. To make that clear to the framework, there is a special attribute, IDReferenceProperty, that you must attach to the TargetButtonID property to signal that it is the button that is referenced.

<ExtenderControlProperty()> _
<DefaultValue("")> _
<IDReferenceProperty(GetType(Button))> _
Public Property TargetButtonID() As String
    Get
        Return GetPropertyStringValue("TargetButtonID")
    End Get
    Set(ByVal value As String)
        SetPropertyValue("TargetButtonID", value)
    End Set
End Property

Having set up the VB, it is time to set up the corresponding JavaScript. Open the behavior file, ChangeAwareButtonBehavior.js and add the one line of code to the ChangeAwareButton.ChangeAwareButtonBehavior function (the last line in the method; we are defining a new member variable to hold the property value for the button's text value).

ChangeAwarebutton.ChangeAwarebuttonBehavior = function(element) 
{
    ChangeAwarebutton.ChangeAwarebuttonBehavior.initializeBase(this, [element]);
    this._TargetButtonIDValue = null;
    this._ButtonTextValue = null;  // we added this line

We'll need accessor functions for the new property as well. The following lines of code get and set the value for _ButtonTextValue:

get_ButtonText : function() 
{
    return this._ButtonTextValue;
},
set_ButtonText : function(value) 
{
    this._ButtonTextValue = value;
},

You can place these either above or below the existing accessors for the TargetButtonID.

Finally, and this is (you should pardon the pun) key to our extender, we want to implement a client-side method to respond to the keyup event in the TextBox control. There is no server-side keyup event, but there is one for the client side, which is what makes AJAX so powerful. Unfortunately, Microsoft does not provide access to this event in any of its extenders, which, after all, is why we are writing our own extender.

We do this as classic JavaScript, putting the declaration in the prototype section, where you find the comment "Add your initialization code here."

$addHandler(this.get_element(), 'keyup',
   Function.createDelegate(this, this._onkeyup));
   this._onkeyup();

Put the method itself inside the class, just below the definition of the dispose method,

_onkeyup : function() 
{
    // set a local variable to represent the button
    var e = $get(this._TargetButtonIDValue);
    if (e)    // if we got the button
    {
        // set the variable disabled to whether the text box is empty
        var disabled = (this.get_element().value == "");
        
        // set the button disabled to the value of the variable
        e.disabled = disabled;
        
        // if the button has text...
        if (this._ButtonTextValue) 
        {
            // if the button is disabled
            if (disabled) 
            {
                // set the control's old value to the current value
                this._oldValue = e.value;
                // set the current value to the button's text value
                e.value = this._ButtonTextValue;
            } 
            else // if the button is not disabled
            {
                if (this._oldValue) // if there is an old value
                {
                    // set the text to the old value
                    e.value = this._oldValue;
                }   // end if _oldvalue
            }       // end else
        }           // end if TextValue
    }               // end if e
},                  // end on key up function

Build the Solution by clicking on Build→Build Solution. This will cause a new section called ChangeAwareButton Components to be added to the top of the Toolbox, as shown in Figure 8.

False Triggers
Figure 8. New Toolbox Section (Click to enlarge.)

There needs to be a component in this toolbox section called ChangeAwareButtonExtender. If it is there, great. Drag it onto the page.

If not, you can manually add the control directly to the page. You need to add to the website a reference to the Control Extender project. Right-click on the website in Solution Explorer. Click on Add Reference…, then click on the Projects tab of the Add Reference dialog box, as shown in Figure 9.

Projects tab
Figure 9. Projects tab of the Add Reference dialog

Switch to the Source view of Default.aspx. Add the following Register directive to the top of the page:

<%@ Register Assembly="ChangeAwareButton"
     Namespace="ChangeAwareButton.ChangeAwareButton " 
     TagPrefix="extndr"%>

Now add a declaration for a ChangeAwareButtonExtender control inside the UpdatePanel, which already contains the TextBox and Button, with the following line of markup:

<extndr:ChangeAwareButtonExtender ID="cab" runat="server" />

Once the ChangeAwareButtonExtender is on the page, switch back to Design view. Click on that control, and then go to the Properties window.

Remember to hook this extender to its target TextBox, by setting the TargetControlID property to the TextBox's ID.

Click on the TextBox. The Properties window will now include an item corresponding to the ChangeAwareButtonExtender. The BehaviorID will already be filled in. Add values for the DisabledButtonText and TargetButtonID subproperties, as shown in Figure 10.

ChangeAwareButtonExtender subproperties
Figure 10. TextBox Properties showing the ChangeAwareButtonExtender subproperties

Run the app. The page will look like Figure 11. The button is initially disabled and its Text property will be what you entered as the DisabledButtonText property of the ChangeAwareButtonExtender.

ChangeAwareButton
Figure 11. ChangeAwareButton, initially disabled

As soon as any text is entered in the text box, the button will become enabled and its Text property will revert to it's original value declared in the Button declaration in the markup, as shown in Figure 12.

ChangeAwareButton enabled
Figure 12. ChangeAwareButton enabled after entering text

Since this example is based on the previous example, clicking the button will call into play the ConfirmButtonExtender, to ask the user if he is sure he wants to delete this user.

In a real application, the Click event handler for the button would do more than just blank the text box. Presumably it would verify that the username actually existed before trying to remove it.

Face Three: Deep Into the DOM

There are times when even a custom extender won't do—when you have to reach down into the Document Object Model (by way of DHTML) and manipulate it directly with JavaScript, calling upon the asynchronous capabilities of the browse directly, not mediated through the Update Panel, but by interacting directly with the XMLHttp object. This is hard core; this is pedal to the metal; this is raw AJAX; this is ugly.

To demonstrate how unvarnished AJAX works, we'll create a word-wheel: a control that allows you to type letters into a text box and see all the city names that begin with the letters that you've typed so far. To do this, we need a database of city names; we'll use the list of cities in the AdventureWorks database.

To begin, copy the previous example to a new website called HardCoreAjax.

Be certain the contents of the bin directory also get copied over.

Open the HardCoreAjax website and set Default.aspx to be the default start page. Run it to make sure it still works correctly.

Add a new page to the website called UserNameLookup.aspx. This page is used as a vehicle for allowing JavaScript on Default.aspx to run some server-side code. When it runs, it's Page_Load method will read the usernames from the database, then construct the HTML to build a select list from the results. It will return this HTML text string to the calling JavaScript, which will then insert it into the Default page asynchronously.

When UserNameLookup.aspx displays in Source view, delete the entire contents of the page except for the Page directive on the first line. Replace the content with a single ASP.NET Literal control, so that the entire contents of the file look like Example 1. You can ignore the red squiggly line under the asp tag of the Literal control, which normally indicates a syntax error.

Example 1. UserNameLookup.aspx

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="UserNameLookup.aspx.vb" 
    Inherits="UserNameLookup" %>

<asp:Literal id="UserNames" runat="server" />

Let's be crystal clear on what this page will do: its job is to look up the city names in the database, assemble the HTML that we want to inject into our Default.aspx page, and put that HTML into the Literal control. We will then insert that HTML into the page at the client, using DHTML and JavaScript.

Getting the City Names

Open the code-behind file for UserNameLookup, UserNameLookup.aspx.vb. Add all the highlighted code from Example 1-2, below. This includes two imports statements outside the class declaration to provide access to the required Data namespaces, one event handler method, Page_Load, and one helper method, UserNamesForPartialName. All of this is straightforward server-side ASP.NET code; nothing special.

When the page loads, it looks at the query string that is passed in and formulates a query to select city names from the database. If it gets back names, its job is to create an HTML statement that will display the cities in a listbox (a Select object). It does so by assembling the HTML line by line. The first line creates the select object: </p>

"<select id='slctName' size=5 onchange=""SelectName();"">"

The code then iterates through all the rows of the data table, and for each city name an option is created, with its value and its text set to the name of the city.

For Each row In dt.Rows
   returnString = returnString + "<option value='" + _
      row("UserName").ToString() + "' >" + _
      row("UserName").ToString() + _
      "</option>"
Next

When all the cities have been accounted for, the select statement is completed.

returnString = returnString + "</select>"

This entire string is then placed into the Literal control, which becomes the entirety of the .aspx page. Thus, this page is nothing more than the HTML of the listbox, which can be placed, as is, right into the Default page markup, in the appropriate <div >, using DHTML.

Example 2 shows how the <select> element is created.

Example 2. UserNameLookup.aspx.vb

Imports System.Data
Imports System.Data.SqlClient

Partial Class UserNameLookup
    Inherits System.Web.UI.Page

   Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
      Handles Me.Load
      If (Request.QueryString.Count > 0) Then
         Dim queryUserName As String = Request.QueryString.Get(0).ToString()
         Dim dt As DataTable = UserNamesForPartialName(queryUserName)

         If (dt Is Nothing Or dt.Rows.Count = 0) Then
            UserNames.Text = _
              "Sorry no user names found with that letter combination."
         Else
            Dim returnString As String = String.Empty
            returnString = returnString + _
              "<select id='slctName' size=5 onchange=""SelectName();"">"
            Dim row As DataRow
            For Each row In dt.Rows
               returnString = returnString + "<option value='" + _
                  row("UserName").ToString() + "' >" + _
                  row("UserName").ToString() + _
                  "</option>"
            Next
            returnString = returnString + "</select>"
            UserNames.Text = returnString

         End If

      Else
         UserNames.Text = String.Empty
      End If
   End Sub

   Public Function UserNamesForPartialName(ByVal strPartialName As String) _
           As DataTable
      Dim connectionString As String = _
        ConfigurationManager.AppSettings("AdventureWorks")
      Dim connection As SqlConnection = New SqlConnection(connectionString)

      Dim queryString As String = _
        "select distinct LastName as UserName from Person.Contact where " + _
        "LastName like '" + _
        strPartialName + "%' order by LastName"
      Dim ds As DataSet = New DataSet()

      Try
         Dim dataAdapter As SqlDataAdapter = _
            New SqlDataAdapter(queryString, connection)
         dataAdapter.Fill(ds, "Names")
      Catch ex As Exception
         '' Handle exception
         UserNames.Text = ex.Message
      Finally
         connection.Close()
      End Try

      Return ds.Tables("Names")

   End Function

End Class
The username is stored in the Login column of the HumanResources.Employee table, in the form adventure-works\username. Rather than parse that out of the field, I cheated and just queried for the LastName column in the Person.Contact table. However, the query uses an alias, UserName, for the column returned. This is so that if you are so inclined, you can modify the query to retrieve the actual username from the database.

Open Default.aspx in Source view. You'll add all the JavaScript inside the <head> element at the top of the page. You'll also add an attribute to the TextBox txtName so that it will point to one of the JavaScript functions to handle the keyup event. Finally, you'll add a <div> element where the JavaScript function can insert the HTML you created in UserNameLookup's code behind.

Default.aspx is unchanged except for the script, so let's walk through the script one piece at a time.

<script language="javascript" type="text/javascript">
      var xmlHttp
      function showHint(str)
      {
         if (str.length==0)
         { 
            document.getElementById("TextBoxHint").innerHTML=""
            return
         }

         xmlHttp = new XMLHttpRequest()

         var url="UserNameLookup.aspx"
         url=url+"?q="+str
         xmlHttp.onreadystatechange=stateChanged 
         xmlHttp.open("GET",url,true)
         xmlHttp.send(null)
      }

The first method, showHint, takes a string (which it will get from the contents of the text box). If the string is empty, it sets the <div> (which we named TextBoxHint) to empty and returns. The innerHTML property of the

sets the text between the opening and closing tag of the <div>.

If the text is not empty, then the function creates XMLHttpRequest to handle asynchronous communication.

This is a bit of a simplification, but will do for now. In the actual downloadable code, we show a few ways to get what we need.

We set the url variable to the name of the aspx page that is going to look up the cities (UserNameLookup.aspx) and append to that name the characters "?q" indicating that we're going to pass in values to the query string object, and then we append whatever value we obtained from the text box (that is, the portion of the city name typed by the user).

var url="UserNameLookup.aspx"
url=url+"?q="+str

We are now ready to open UserNameLookup.aspx and wait for its processing to be completed, asynchronously. We need a call back so that we are notified when the xmlHttp object's onreadystatechange method fires (which it will when the called page finishes its work), and we assign the name of the method state changed:

xmlHttp.onreadystatechange=stateChanged

Having done, so we open the page and then send null to force the method call and to put us into a wait state.

Each time the onreadystatechange event fires, our stateChanged method is called and we examine it to see if the page is complete (ready state is complete or value 4) and the status is OK. If so, we take the responseText from the xmlHttp object and we assign it to the innerHTML of the Div, thus putting the listbox where we earlier had nothing!

 function stateChanged() 
 { 
    var OK = 200
    if (( xmlHttp.readyState == 4 || xmlHttp.readyState == "complete" ) 
      && xmlHttp.status == OK )
    { 
       document.getElementById("TextBoxHint").innerHTML = xmlHttp.responseText 
    } 
 }

You will need to add a connection string to web.config so that UserNameLookup can connect to the database. Add the following code snippet to web.config just before the <system.web> element:

<appSettings>
   <add key="AdventureWorks" value="Data Source=ServerName;
      Initial Catalog=AdventureWorks;Integrated Security=True;" />
</appSettings>

Run the app and type a character in the text box. The select list will appear below the text box and, as with the previous example, the button will become enabled and its text will change. Type a second character, and the list in the select box will narrow.

False Triggers
Figure 13. HardCoreAjax in action (Click to enlarge.)

Next Steps

We believe strongly that you can go for a long time using AJAX with the controls provided by Microsoft, and we strongly recommend compiling and exploring the samples provided with the Control Toolkit.

For more on extenders, the best bet is to start off with our sample and then to vary it, probably with a good book on JavaScript by your side (we recommend JavaScript The Definitive Guide by David Flanagan).

If you decide to dive into DHTML and to write raw AJAX code, our strongest recommendation is Dynamic HTML, the Definitive Reference by Danny Goodman, which was specifically updated for AJAX. Goodman is a wonderful writer.

All of the material in this article is expanded on for the new ASP.NET programmer in our forthcoming book Learning ASP.NET by Jesse Liberty and Dan Hurwitz. For a somewhat different perspective on the same material, it is also covered in Programming .NET 3 by Jesse Liberty and Alex Horovitz.

The code for this article is available on Jesse's website: http://www.GotDotNet3.com along with links to a support forum, errata, and other items of interest.

Jesse Liberty is a senior program manager for Microsoft Silverlight where he is responsible for the creation of tutorials, videos and other content to facilitate the learning and use of Silverlight. Jesse is well known in the industry in part because of his many bestselling books, including O'Reilly Media's Programming .NET 3.5, Programming C# 3.0, Learning ASP.NET with AJAX and the soon to be published Programming Silverlight.

Dan Hurwitz is the president of Sterling Solutions, Inc., where for nearly two decades he has been providing contract programming and database development to a wide variety of clients.


Return to the Windows DevCenter.

Copyright © 2009 O'Reilly Media, Inc.