Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Ajax

Download as doc, pdf, or txt
Download as doc, pdf, or txt
You are on page 1of 12

AJAX (Asynchronous JavaScript and XML)

Introduction
Since the start of Web programming, numerous tradeoffs have existed
between Web applications and desktop applications. For example, it has been
generally accepted that Web applications don't provide the same type of rich user
interface as desktop applications. On the flip side, Web applications are platform
independent and provide an easier development mechanism. One area that's been a
continuous battleground for Web developers has been the seemingly simple task of
providing more responsive applications.
Traditionally, a response to a user's input could only be retrieved by
submitting a new request to the Web server. In some cases, developers could load all
responses on the client (using JavaScript) and provide a better user experience. A
common example of this technique is to dynamically load a list of states or
provinces, based on a selected country. Unfortunately, in many cases, neither posting
back nor loading everything into JavaScript felt correct. Either posting back created
too much of a UI disconnect, or an unmanageable amount of data was required on
the client (which often resulted in less-than-readable JavaScript). AJAX provides a
new in-between alternative, capable of leveraging the server-based application while
maintaining a responsive and slick feel.

What Is AJAX?
AJAX, short for Asynchronous JavaScript And XML, isn't a technology but
rather a grouping of technologies. AJAX uses a communication technology (typically
SOAP and XML) to send and receive an asynchronous request/response to the server,
and then leverages presentation technologies (JavaScript, DOM, HTML, and CSS) to
process the response. Applications using AJAX are legitimate today, because most
browsers support the necessary technology. For a more detailed definition of AJAX,
visit the AJAX Wikipedia entry.
What does AJAX really mean? It lets you execute a server-side method
through a JavaScript call, without requiring a browser refresh. Think of it as a mini
request/response that happens behind the scenes from the user. If you still aren't
clear what AJAX is, take a look at two popular examples from Google: Google
Suggests and Google Maps. If you are new to AJAX, the responsiveness of those two
applications should make your skin tingle slightly.

AJAX for ASP.NET


A lot of plumbing goes into making AJAX tick. Chances are that you don't
want to spend hours or days figuring out the internals of AJAX, but would rather start
creating AJAX-enabled applications today, to meet existing demands (and if you do
want to know how it works internally, I'm certainly not the person to ask). There are
a number of tools developers can use to get started quickly. Specifically, we'll look at
the free and open-source Ajax.NET written by Michael Schwarz. Ajax.NET takes care
of all the implementation details, is .NET-aware, and can be extended. Microsoft
ASP.NET 2.0 introduces its own flavor of asynchronous callbacks through the Client
Callback functionality, and it was recently announced that an AJAX implementation,
code-named "Atlas," was in the works.
The terminology might be confusing, but when I talk about AJAX, I'm talking
about the overall framework for asynchronously calling server-side functions from
the client. When I mention Ajax.NET, I'm referring to a specific implementation that
helps you build solutions that take advantage of the AJAX framework.

AJAX Hands-On
The remainder of this article will mostly be devoted to three meaningful
examples that utilize the power of AJAX using Ajax.NET. The guide will contain code
in both Microsoft C# and Microsoft Visual Basic .NET, sometimes both will be
provided, and sometimes only one or the other. The code to make all this happen is
so easy, that C# developers should be able to easily follow the Visual Basic .NET-only
code, and vice versa! Sample C# and Visual Basic .NET projects are included with
this article for download, and provide working and running code. Before we get to
the examples, a primer on setting up and working with Ajax.NET is necessary.

Ajax.NET

The AJAX.NET documentation and Web site do a good job at getting


developers up and running. We'll briefly go over the core steps you need to know
before looking at some concrete examples of this technology in use.
Start by downloading and unzipping the AJAX file from the AJAX.NET project
Web site, creating a new ASP.NET project (in either Visual Basic .NET or C#,
whichever you prefer) and adding a reference to the AJAX.dll file. The only
configuration step beyond that is to add the following code in the web.config file,
inside the <system.web> element.

<configuration>
<system.web>
<httpHandlers>
<!-- Register the ajax handler -->
<add verb="POST,GET" path="ajax/*.ashx"
type="Ajax.PageHandlerFactory, Ajax" />
</httpHandlers>
...
...
</system.web>
</configuration>

In order to make server-side functions available through JavaScript, two


things must be done. First, the function or functions in question must be marked
with the Ajax.AjaxMethodAttribute. Second, the class containing these functions
must be registered through a call to Ajax.Utility.RegisterTypeForAjax during the
page load event. Don't worry if it sounds complicated; it's really only two extra lines
in your code. Let's look at an example.
//C#

public class Sample : System.Web.UI.Page


{
private void Page_Load(object sender, System.EventArgs e)
{
//Register the class containing the server-side function
//we are interested in
Ajax.Utility.RegisterTypeForAjax(typeof(Sample));
}
[Ajax.AjaxMethod()]
public string GetMessageOfTheDay()
{
return "Experience is the mother of wisdom";
}
}
'VB.NET
Public Class Sample
Inherits System.Web.UI.Page
Private Sub Page_Load(sender AsObject, e As EventArgs)
Handles MyBase.Load
'Register the class containing the server-side function
'we are interested in
Ajax.Utility.RegisterTypeForAjax(GetType(Sample))
End Sub
<Ajax.AjaxMethod()> _
Public Function GetMessageOfTheDay() As String
Return "Experience is the mother of wisdom"
End Function
End Class
The above example first tells Ajax.NET to look at the Sample class for Ajax-
friendly methods. This happens to be the same class as the actual page, but it could
be any .NET class, or multiple classes could be registered. Ajax.NET will then look
through the specific class for any methods that are marked with the
AjaxMethodAttribute, of which the Sample class has one, GetMessageOfTheDay.
With that done, the only thing left to do is to make use of it in JavaScript.
Ajax.NET automatically creates a JavaScript variable named the same as the
registered class (in this example it will be Sample) that exposes functions named
the same as the AjaxMethod (in this example, GetMessageOfTheDay). Here's
what it looks like.
<script language="javascript">
Sample.GetMessageOfTheDay(GetMessageOfTheDay_CallBack);
function GetMessageOfTheDay_CallBack(response)
{
alert(response.value);
}
</script>

The JavaScript GetMessageOfTheDay expects the same parameters as its


server-side counterpart (in this case, none) in addition to the JavaScript callback
function to execute and pass the response to when done. Here we see the
asynchronous nature of AJAX at work, since the call to GetMessageOfTheDay
doesn't prevent other JavaScript code from executing, nor does it prevent the user
from continuing to work on the page. When the server-side processing is done,
Ajax.NET calls the specified callback function, GetMessageOfTheDay_CallBack,
and passes it the response that comprises the server-side's return value.
The mapping between server-side code and JavaScript code might be
confusing. Figure 1 shows the outline of both server-side code and JavaScript code,
along with the mapping between the two.

Sample 1: Linked Drop-Down List

The beginning of this article briefly discussed the two traditional approaches
used for linking one DropDownList to another. Either the page is posted back when
the selected index changes or all the possible data is loaded into JavaScript arrays
and dynamically displayed. Hopefully you can see how AJAX might be used as an
alternative to both of these solutions.
First, let's look at our data interface and drive the example from there. Our
data access layer will expose two methods: the first will retrieve a list of countries
our system supports, while the second will take a country ID and return a list of
states/provinces. Since this is pure data access, we'll just look at the method
signatures.
//C#
public static DataTable GetShippingCountries();
public static DataView GetCountryStates(int countryId);
'VB.NET
Public Shared Function GetShippingCountries() As DataTable
Public Shared Function GetCountryStates(ByVal countryId As Integer) As
DataView

Now, let's flip to the opposite layer and create our simple Web form.

<asp:DropDownList ID="countries" Runat="server" />


<asp:DropDownList ID="states" Runat="server" />
<asp:Button ID="submit" Runat="server" Text="Submit" />
The Page_Load event is equally straightforward, and is as common as the preceding
Web form. We use our data access layer to retrieve the available countries and bind
that to our countries DropDownList.
//C#
if (!Page.IsPostBack)
{
countries.DataSource = DAL.GetShippingCountries();
countries.DataTextField = "Country";
countries.DataValueField = "Id";
countries.DataBind();
countries.Items.Insert(0, new ListItem("Please Select", "0"));
}
This is where our code stops being typical. First, we'll create our server-side function
that we want to call from JavaScript.
'VB.NET
<Ajax.AjaxMethod()> _
Public Function GetStates (ByVal countryId As Integer) As DataView
Return DAL.GetCountryStates(countryId)
End Function
This is like any other function you'd normally have: it expects the ID of the country
we want to get, and passes the request to the DAL. The only difference is that we've
marked the method with the AjaxMethodAttribute. The last server-side step left to
do is register our class containing the above method (in this case it's our code-
behind) with Ajax.NET through a call to RegisterTypeForAjax.

//C#

Ajax.Utility.RegisterTypeForAjax(typeof(Sample));
'VB.NET

Ajax.Utility.RegisterTypeForAjax(GetType(Sample))

We're almost done; all that's left is to call the GetStates method from
JavaScript and handle the response. We logically want to call GetStates when the
user selects a new item from the list of countries. To do so, we'll hook into the
JavaScript onChange event. Our Web form code thus changes slightly.

<asp:DropDownList onChange="LoadStates(this)" ID="countries"


Runat="server" />

The JavaScript LoadStates function will be responsible for issuing the


asynchronous request through the proxy created by Ajax.NET. Remember that, by
default, the proxy Ajax.NET created is of the form
<RegisteredTypeName>.<ServerSideMethodName>. In our case, that'll be
Sample.GetStates. We'll also want to pass in our country ID parameter and the
callback function that Ajax.NET should call once the server-side function is done.
//JavaScript
function LoadStates(countries)
{
var countryId = countries.options[countries.selectedIndex].value;
Sample.GetStates(countryId, LoadStates_CallBack);
}
Finally, the last step is to handle the response in our LoadStates_CallBack
function. Probably the most productive feature of Ajax.NET is its support for a
number of .NET types (I've mentioned this a few times already). Recall that our
server-side function returned a DataView. What does JavaScript know of
DataViews? Nothing, but JavaScript is an object-oriented language, and Ajax.NET
takes care of not only creating an object similar to the .NET DataView, but also
maps the return value from the function to the JavaScript clone. You should keep in
mind that the JavaScript DataView is merely a replica of the actual DataView, and
doesn't (currently) support much more functionality than looping through the rows
and accessing the column values (functionality such as setting the RowFilter or Sort
properties).

function LoadStates_CallBack(response) { //if the server-side code


threw an exception if (response.error != null) { //we should probably
do better than this alert(response.error); return; } var states =
response.value; //if the response wasn't what we expected if (states ==
null || typeof(states) != "object") { return; } //Get the states drop
down var statesList = document.getElementById("states");
statesList.options.length = 0; //reset the states dropdown //Remember,
its length not Length in JavaScript for (var i = 0; i < states.length;
++i) { //the columns of our rows are exposed like named properties
statesList.options[statesList.options.length] = new
Option(states[i].State, states[i].Id); } }

After a bit of error checking, the preceding JavaScript gets the states drop-down list,
loops through the response's value, and dynamically adds options to the drop-down
list. The code is clean, straightforward, and eerily similar to C# and Visual Basic
.NET. Personally, as a developer who's created JavaScript arrays based on server-side
variables and linked them together, I still have a hard time believing it actually
works.
There is one major issue that might not be obvious. Since the DropDownList was
dynamically created in JavaScript, its items aren't part of the ViewState and won't
be maintained. That means that our button's OnClick event handler needs to do
some extra work.
'VB.NET
Private Sub submit_Click(sender As Object, e As EventArgs)
Dim selectedStateId As String = Request.Form(states.UniqueID)
'should do some user validation...
states.DataSource =
DAL.GetCountryStates(Convert.ToInt32(countries.SelectedIndex))
states.DataTextField = "State"
states.DataValueField = "Id"
states.DataBind()
states.SelectedIndex =
states.Items.IndexOf(states.Items.FindByValue(selectedStateId))
End Sub
First, we can't use the states.SelectedValue property and must use Request.Form.
Second, if we want to redisplay the list to the user, we need to bind the states
DropDownList, thankfully reusing the same data access method. Finally, the
selected value has to be programmatically set.

Sample 2: Document Locker

For our next example, we'll take a more complete feature and improve it with
AJAX. This example will be of a simple document management system. Like any
decent document management system, we have to provide concurrency
management. That is, we need a way of handling two users trying to edit the same
document. We'll do so by creating some type of locking mechanism that prevents
one user from editing a document already being edited. We'll use AJAX to make the
user's experience with our locking mechanism more pleasant. First, we'll create a
queue of documents the user tried to edit but couldn't (because it was already being
edited), and automatically notify him or her when they become available. Second,
we'll make sure to unlock a document if the user closes his or her browser or
navigates away. This last feature helps ensure that documents don't stay forever
locked. For the purpose of this guide, we'll skip the functionality not related
specifically to the AJAX implementation; however, the downloadable project contains
everything.
First, when a user tries to edit a document, we'll try to acquire an exclusive
lock on it and, failing that, we'll add the document to the user's queue and send him
or her back to the main page. There's nothing specific to AJAX here, but we'll look at
the code in order to give the example the necessary context. In the OnLoad event of
the Page used for editing, the following is added.
//C#

if (!Page.IsPostBack)

//should validate user input

Document document = GetDocument(Request.QueryString["id"]);

//We have the document, but can't edit it!

if (!Locker.AcquireLock(document))

//let's add it to the user's list of docs to watch


User.CurrentUser.AddDocumentToQueue(document.DocumentId);
Response.Redirect("DocumentList.aspx");

//ok, we have the document and CAN edit it //...

The key line is where the document is added to the current user's queue,
which adds it to the session. Next, we'll create a user control, which can be placed on
any page, used to notify the user when a queued document has become available.
This user control will contain a single AJAX method, along with the code necessary to
register the class with AJAX.

'VB.NET Private Sub Page_Load(s As Object, e As EventArgs) Handles


MyBase.Load Ajax.Utility.RegisterTypeForAjax(GetType(UnlockNotifier))
End Sub 'Loops through the queued documents and checks if they're
available <Ajax.AjaxMethod()> _ Public Function GetUnlockedDocuments()
As DocumentCollection 'Get all the queued document ids belonging to the
user Dim queuedDocument As ArrayList = User.CurrentUser.DocumentQueue
Dim unlocked As DocumentCollection = New DocumentCollection For Each
documentId As Integer In queuedDocumentIds 'If the queued document is
no longer locked If Not Locker.IsLocked(documentId) Then
unlocked.Add(Document.GetDocumentById(documentId)) End If Next Return
unlockedDocuments End Function

All that's needed now is some JavaScript to make the request and handle the
response. We'll place the released document information, if any, inside a table that
we'll dynamically build, based on the response. To do so we'll start off with our
HTML.
<div id="notifyBox" style="display:none;">
<b>The following queued documents can now be edited</b>
<table cellpadding="5" cellspacing="0" border="0"
style="border:1px solid #EEE;" id="notifyTable">
</table>
</div>
We use the DIV tag to hide everything when no documents are available (or maybe
none are queued for the user), and the TABLE tag to display the results. We'll use a
polling system to see if any queued documents are available. Basically, this means
we'll keep calling the server-side method, with a delay, and display the results. The
first call will simply occur when the page loads, and subsequent calls will be timed to
happen every X seconds.

<script language="javascript"> window.setTimeout("PollQueue();", 2000);


//fires every 2 seconds to check if a queued document was released //in
a real system with a lot of users, 2 seconds might put too high //a
load on the server. We could even check first to see if the user //even
has anything queued, but we'll certainly need to do some //performance
testing function PollQueue() { //UnlockNotifier is the type we
registered with Ajax.NET //GetUnlockedDocuments is a method within that
type marked with //the AjaxMethod attribute
UnlockNotifier.GetUnlockedDocuments(PollQueue_CallBack); //calls itself
every 2 seconds window.setTimeout("PollQueue();", 2000); } </script>

All that's left is to handle the response. This is similar to the code from the
previous example. First, check for any errors, get the response, loop through the
available documents, and dynamically build our HTML, in this case adding rows and
columns to our table.
function PollQueue_CallBack(response) { var notifyBox =
document.getElementById("notifyBox"); var notifyTable =
document.getElementById("notifyTable"); //if we couldn't find our table notification box
if (notifyBox == null || notifyTable == null) { return; } //if the server-side code threw an
exception if (response.error != null) { notifyBox.style.display = "none";
alert(response.error); //we should probably do better than this return; } var documents =
response.value; //if the response wasn't what we expected if (documents == null ||
typeof(documents) != "object") { notifyBox.style.display = "none"; return; } for (var i =
0; i < notifyTable.rows.length; ++i) { notifyTable.deleteRow(i); } for(var i = 0; i <
documents.length; ++i) { var row = notifyTable.insertRow(0); row.className = "Row" +
i%2; var cell = row.insertCell(0); cell.innerHTML = documents[i].Title; cell =
row.insertCell(1); var date = documents[i].Created; cell.innerHTML = date.getDay() + "/"
+ date.getMonth() + "/" + date.getYear(); cell = row.insertCell(2); cell.innerHTML = "<a
href='DocumentEdit.aspx?id=" + documents[i].DocumentId + "'>edit</a>"; }
notifyBox.style.display = "block"; }

The last quick improvement we'll look at is to automatically unlock a


document being edited if the user closes his or her browser, navigates to another
link, or clicks the back button. Typically, this is achieved by hooking into the
JavaScript OnBeforeUnLoad event or OnUnload event, opening a new small popup
that does some cleanup on page load, and then closes itself down. While your own
use of popups might be legitimate, others don't play so nice, resulting in popup
blocking and documents that stay locked forever. To solve this problem, we'll still rely
on the two JavaScript events, but instead of launching a popup, we'll execute a
server-side method through AJAX. On our page used to edit a document, the one
that places a lock, we add some simple JavaScript.
<script language="javascript"> //Makes sure the document is unlocked if
the user closes the //browser or hits the back button
window.onbeforeunload = ReleaseLock; function ReleaseLock()
{ Locker.ReleaseDocument(<%=DocumentID%>); } </script>

Here, DocumentId is a variable defined and set in code behind. Alternatively we


could store DocumentId in the session and access that in the server-side
ReleaseDocument. ReleaseDocument basically removes the document from the
list of locked documents.

Sample 3: Forums Subject Search

The last example we'll look at will be the modification of an existing app. I
heard this idea first proposed by Josh Ledgard as a feature being played with for the
MSDN forums. The goal of it is to try to help users with questions help themselves,
as well as to curb the number of duplicate posts. Basically, when a user goes to ask a
new question in the forum, he or she enters a subject and a question, all too often
not doing a search to see whether the question was already asked and answered.
Enter AJAX. After the user finishes entering the subject (and tabs out of the field),
we'll asynchronously search the forums, based on the subject, and non-obtrusively
present the results to the user. Sometimes the results will be helpful, sometimes not.
To accomplish this, we'll modify the asp.NETPRO Reader's Choice Award for
Best Forum Application, CommunityServer. The downloadable samples doesn't
include the code in this section (or the forums), but you can learn more about
CommunityServer at http://communityserver.org/ and apply the following code
snippets to it.
With CommunityServer installed and Ajax.NET configured (references and
handler added to web.config), we need only make a few changes to get the desired
functionality. First, we'll go to the CreateEditPost.cs file in the
CommunityServerForums project. Think of this as the code-behind for the page
where users go to add a new post. Here we'll add our AJAX-enabled function.

//C# [Ajax.AjaxMethod()] public static ArrayList Search(string search)


{ SearchQuery query = new SearchQuery(); query.PageIndex = 0; //get the
first 10 results query.PageSize = 10; query.UserID =
Users.GetUser().UserID; query.SearchTerms = search; return new
ForumSearch().GetSearchResults(query).Posts; }

We are able to leverage the search functionality already built into


CommunityServer and simply have our function wrap around it. As always, the type
must be registered with Ajax.NET. We'll do this in the InitializeSkin function of the
same file (think of it as Page_Load).

//C# Ajax.Utility.RegisterTypeForAjax(typeof(CreateEditPost));

Before we can jump into JavaScript, we need to make one final server-side change.
Custom classes returned to Ajax.NET, such as the ForumPost contained in the
ArrayList we are returning, must be marked with the Serializable attribute. All we
do is go into the Components/ForumPost.cs file of the
CommunityServerForums project, and add the attribute.
//C# [Serializable] public class ForumPost : Post { ... }

At the presentation side, we need only modify


Themes/default/Skins/View-EditCreatePost.cs in the CommunityServerWeb
project. First, we'll hook into the onBlur event of the subject's textbox.

<asp:textbox onBlur="Search(this.value);" id="PostSubject"


runat="server" ... />

Next, we write the JavaScript Search method so that it calls our server-side Search.

var oldValue = ''; function Search(value) { //don't search again for


something we just searched //would happen if the user is tabbing back
and forth if (value != oldValue) { CreateEditPost.Search(value,
Search_CallBack); oldValue = value; } }

Finally, all that's left is to handle the response. Since the previous example
showed a slightly more elegant way to display results in a table, we'll merely create
some dynamic HTML and stick it into a fictitious DIV.

function Search_CallBack(response) { //since the search functionality


automatically redirects if there //are no results, we can't rely on
response.error var results = response.value; //if we didn't get a
result if (results == null) { return; } //a div that we'll use to put
our results var someDiv = document.getElementById("someDiv"); var html
= ""; for (var i = 0; i < results.length; ++i) { var result =
results[i]; html += "<a target=_blank href='" + result.PostID html +=
"/ShowPost.aspx'>"; html += result.Subject; html += "</a><br />" }
someDiv.innerHTML = html; }

By making minor modifications to three files (plus the web.config for


configuration) of the CommunityServer application, we were able to add some pretty
nifty functionality. However, do be cautious about simply adding AJAX-enabled
functionality to an existing application. The preexisting ForumSearch class doing
the actual search might not have been designed for the type of use we've introduced.
Our code will likely result in a number of extra searches being performed, and the
impact could be significant.

AJAX and You


How and where AJAX fits into your applications, and whether they already
exist or not, is going to be very situational. Although we've seen how easy it is to
create AJAX-enabled solutions with Ajax.NET, there are other considerations to take
into account. A serious concern is the impact on the overall architecture and
maintainability of your application. AJAX can further blur the line between your
system's layers, notably the presentation, presentation logic, and business layers.
This isn't an issue with AJAX itself, but rather with how you'll make use of it. As long
as you are aware of how easy it might be to cause some bleeding between layers,
and only do so calculatingly, all should be good.

Will an application that uses AJAX be harder to maintain? The answer largely
depends on how much JavaScript you are already using, and how good you are at
organizing and maintaining it. Many developers feel that JavaScript is harder to
write, test, and debug (not because of JavaScript itself, but because of tool support
and developer knowledge). If you are currently implementing a linked drop-down list
using JavaScript, and switch to AJAX, your code will probably get easier to maintain
(Ajax.NET's support for .NET types and arrays is a big reason for this). However, if
you are going the post-back route, you'll now be introducing a whole new language
to your application (JavaScript), and you'll have to deal with having some data not
participating in ViewState (as we saw in the button click event).

Another consideration is the impact AJAX will have on the usability of your
Web site. Even though the ultimate gift of AJAX is to create more responsive
interfaces, developers should keep two things in mind. First, and most obviously,
AJAX is dependent on JavaScript. We all know that some users disable JavaScript,
and that some standards (such as the Canadian government Common Look and Feel
[think Canada's 508]) require that Web sites work with or without JavaScript. So,
you shouldn't assume that the AJAX functionality is working, and you should have
your application fall back to more normal Web processing if it isn't available. Second,
AJAX applications might be unfamiliar (even if it's superior) to the way users are
used to using applications. An example of this is that a page that does various things
through AJAX might not behave with the Back button, Favorites menu, and other
browser features in the way a user thinks it should.

Conclusion
AJAX isn't just a hyped upcoming technology; instead, it's a concrete
framework that can provide you with alternative solutions for your everyday
problems when building Web applications. Ajax.NET makes it easy for ASP.NET
developers to embrace AJAX. The three examples we looked at, along with the
downloadable projects, are meant to help you understand how to use AJAX and
Ajax.NET, and also provide a simple playground for you to try out some of your own
ideas. AJAX is about more than creating neat and cool applications; it can truly lead
to improved customer satisfaction and a competitive advantage. Some of the high-
level concepts being discussed for Atlas could significantly improve on the products
we deliver. Personally, the best implementations of AJAX I've seen are fairly
lightweight and just feel right. Your own implementations should be an equally
positive experience for your users. Remember though, given a problem, AJAX
probably won't be the only solution, and it might not be the best. Now, let's prove
that the ASP.NET community is second to none, and let's clean house.

You might also like