Introduction
This is the second article in a three article series focusing on AJAX or Asynchronous JavaScript and XML. If you haven�t read Part 1, I encourage you to read Part 1 of this article series. In Part 1, we had covered the client side portion of AJAX, specifically the JavaScript object used to initiate asynchronous web requests. The goal of this article is to provide something easy for the server side (ASP.NET) developer to integrate into their code (preferably without a lot of fancy or proprietary workarounds).
The Good Stuff
We will be going over the example code (see Download source files above) shown in listings 1 and 2, Article.aspx and Article.aspx.cs respectively. The code samples are shown in C# but the technique can be used with any .NET compliant language.
Article.aspx
Article.aspx is an example of how you might apply AJAX to a real web application. More and more web sites are requiring users to register before they can access most of the sites� content. This usually involves selecting a username and password as well as entering some other information like an email address or zip code. Rarely does a site allow two users to have the same username. So let�s say I choose �JohnDoe� as my username, I click the button to sign up, wait a while, only to have the web page reload saying that the username I have requested is not available. This process could go on for a while until I choose a unique username. AJAX allows us to validate the username as the user is typing it. No posting back to the server and no downloading a complete list of unavailable usernames. Article.aspx shows this �instant validation� in action.
The JavaScript
To validate the username being entered, we need to send the current username to the server, wait for the request to complete, then take action based on the result. I have simplified this process by creating the CallBackObject
(see AJAX Was Here � Part 1 for the details).
var Cbo = new CallBackObject();
Cbo.OnComplete = Cbo_Complete;
Cbo.OnError = Cbo_Error;
Here we are creating a new CallBackObject
and telling the object, �When my web request is completed, I want you to run the function Cbo_Complete
�, and �If anything bad happens during the request, I want you to run the function Cbo_Error
�. Since the web request is asynchronous, we don�t know when exactly the server will complete our request, so we setup the OnComplete
event so we don�t have to sit and twiddle our thumbs.
function Cbo_Complete(responseText, responseXML)
{
var msg = document.getElementById('lblMessage');
if( responseText == 'True' )
{
msg.innerHTML = 'CallBack - Username Available!';
msg.style.color = 'green';
}
else
{
msg.innerHTML = 'CallBack - Username Unavailable!';
msg.style.color = 'red';
}
}
When our web request does finish, we want to let the user know. Our web request will return True
if the username entered is available, or False
if the username entered is not available. If the username is available, we show a positive message with green text, otherwise, we show a negative message in red text.
function Cbo_Error(status, statusText, responseText)
{
alert(responseText);
}
If an error occurs during the web request, we show the error using a standard alert box.
function CheckUsername(Username)
{
var msg = document.getElementById('lblMessage');
if( Username.length > 0 )
{
Cbo.DoCallBack('txtUsername', '');
}
else
{
Cbo.AbortCallBack();
msg.innerHTML = '';
}
}
The function CheckUsername
starts the asynchronous request (or Call Back) to the server. First, we make sure that the username in question is not blank, and then we call Cbo.DoCallBack
, passing the ID of the username input box (more on this later). If the username is blank, we cancel any Call Back currently in process, and clear any messages.
<asp:TextBox id=txtUsername
onkeyup=CheckUsername(this.value);
OnTextChanged="txtUsername_TextChanged"
Runat="server"></asp:TextBox>
Finally, within the HTML, we set the onkeyup
attribute of our username textbox to execute CheckUsername
and pass the current value of the textbox. We use onkeyup
so that we can provide instant feedback to the user as they are entering their desired username.
That is all the client side code required to make our AJAX example work.
The ASP.NET
The beauty of the CallBackObject
is that it essentially allows JavaScript code to fire server side events. Let�s look at the HTML snippet again.
<asp:TextBox id=txtUsername
onkeyup=CheckUsername(this.value);
OnTextChanged="txtUsername_TextChanged"
Runat="server"></asp:TextBox>
Notice how we set OnTextChanged
(which is a server side event) to txtUsername_TextChanged
. As we will see in a moment (see Listing 2), txtUsername_TextChanged
is an ASP.NET event written in C# that determines if the value of txtUsername
is an available username. This event is raised from the client using JavaScript by this line:
Cbo.DoCallBack('txtUsername', '');
Isn�t that cool? You are raising server side events with client side code without reloading the entire page. Let�s look a little deeper at txtUsername_TextChanged
.
protected void txtUsername_TextChanged(object sender, System.EventArgs e)
{
if( !CallBackHelper.IsCallBack )
return;
string uName = txtUsername.Text;
try
{
CallBackHelper.Write( IsUsernameAvailable(uName).ToString() );
}
catch( Exception ex )
{
CallBackHelper.HandleError( ex );
}
}
First off, we check to see if the current request is a Call Back, using the CallBackHelper
(included in the source files). This is exactly the same as using Page.IsPostBack
, you want to do different things depending on the context of the request. Next, we get the value of txtUsername
and pass it to our IsUsernameAvailable
function. This function returns a boolean indicating whether or not the username is available. Finally, we write that value back to the client using CallBackHelper.Write
. Notice how we wrap the processing in a try
/catch
block and if anything bad happens, we use CallBackHelper.HandleError
. This makes sure that the client is notified of errors.
private bool IsUsernameAvailable( string Username )
{
bool isAvailable = true;
switch( Username.ToLower() )
{
case "bill":
case "william":
case "christopher":
case "pierce":
case "zonebit":
isAvailable = false;
break;
}
return isAvailable;
}
IsUsernameAvailable
is a simple test function, normally you would lookup the requested username in a database to see if it was valid.
I have also but an asp:button
on the page to show the difference between our call back implementation and the standard ASP.NET implementation. If you click the �Check Username Availability� button, you post back the entire page to validate the username, which may take a while longer, plus the user cannot continue filling out the form until the server returns the results.
Test It out
Cruise on over to this site to see AJAX in action. Type a letter, and you should get a visual indicator of the availability of the username. Continue typing and you should be updated as you go (try bill or pierce for an unavailable username). You can also click the button to perform the same action using a standard post back.
But what does it all mean?
We could have returned any string to the client, including XML. In this example we only need a simple true
/false
to indicate if the username is available. Another example might be sending a zip code from the client and returning the city and state from the server. The sky is the limit now that you can interact with server side code from the client.
Conclusion
ASP.NET takes care of the event plumbing for us. As long as you use ASP.NET controls and implement standard events, SelectedIndexChanged
, TextChanged
, Click
, you can easily use AJAX in your ASP.NET applications with the CallBackObject
and CallBackHelper
.
And then?
If you�re not already tired of reading my rants, check out Part 3 of the article series when we create an Auto Complete Textbox. Auto Complete Textbox is an ASP.NET control that completes your text as you type. Awesome!
Listing 1 - Article.aspx
<%@ Page language="c#" Codebehind="Article.aspx.cs"
AutoEventWireup="false" Inherits="AJAX.Article" %>
<HEAD>
<title>AJAX was Here</title>
<script type="text/javascript" src="CallBackObject.js"></script>
</HEAD>
<form id="frmAjax" method="post" runat="server">
<script type="text/javascript">
var Cbo = new CallBackObject();
Cbo.OnComplete = Cbo_Complete;
Cbo.OnError = Cbo_Error;
function CheckUsername(Username)
{
var msg = document.getElementById('lblMessage');
if( Username.length > 0 )
{
Cbo.DoCallBack('txtUsername', '');
}
else
{
Cbo.AbortCallBack();
msg.innerHTML = '';
}
}
function Cbo_Complete(responseText, responseXML)
{
var msg = document.getElementById('lblMessage');
if( responseText == 'True' )
{
msg.innerHTML = 'CallBack - Username Available!';
msg.style.color = 'green';
}
else
{
msg.innerHTML = 'CallBack - Username Unavailable!';
msg.style.color = 'red';
}
}
function Cbo_Error(status, statusText, responseText)
{
alert(responseText);
}
</script>
<table width="100%">
<tr>
<td>Username:</td>
<td>
<asp:TextBox Runat="server" ID="txtUsername"
onkeyup="CheckUsername(this.value);"
OnTextChanged="txtUsername_TextChanged" />
</td>
<td align="left" width="100%">
<asp:Label Runat="server" ID="lblMessage"/>
</td>
</tr>
<tr>
<td colspan="3" align="left">
<asp:Button Runat="server" ID="btnCheckUsername"
OnClick="btnCheckUsername_Click"
Text="Check Username Availability" />
</td>
</tr>
</table>
</form>
Listing 2 - Article.aspx.cs
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using WCPierce.Web;
namespace AJAX
{
public class Article : System.Web.UI.Page
{
protected System.Web.UI.WebControls.TextBox txtUsername;
protected System.Web.UI.WebControls.Label lblMessage;
protected System.Web.UI.WebControls.Button btnCheckUsername;
private void Page_Load(object sender, System.EventArgs e) { }
#region Web Form Designer generated code
protected void txtUsername_TextChanged(object sender, System.EventArgs e)
{
if( !CallBackHelper.IsCallBack )
return;
string uName = txtUsername.Text;
try
{
CallBackHelper.Write( IsUsernameAvailable(uName).ToString() );
}
catch( Exception ex )
{
CallBackHelper.HandleError( ex );
}
}
protected void btnCheckUsername_Click(object sender, System.EventArgs e)
{
string uName = txtUsername.Text;
if( IsUsernameAvailable( uName ) )
{
lblMessage.Text = "Server - Username Available!";
lblMessage.ForeColor = Color.Green;
lblMessage.Visible = true;
}
else
{
lblMessage.Text = "Server - Username Unavailable!";
lblMessage.ForeColor = Color.Red;
lblMessage.Visible = true;
}
System.Threading.Thread.Sleep(5000);
}
private bool IsUsernameAvailable( string Username )
{
bool isAvailable = true;
switch( Username.ToLower() )
{
case "bill":
case "william":
case "christopher":
case "pierce":
case "zonebit":
isAvailable = false;
break;
}
return isAvailable;
}
}
}
History
- 2005-04-21 � Initial release.