Introduction
AJAX (Asynchronous JavaScript And XML) has been possible in the major browsers for
a very long time, but it is becoming very popular nowadays. It allows a background
communication (without postbacks), using JavaScript and an object named XmlHttpRequest.
This object can send synchronous and asynchronous HTTP requests to the web server
(for security reasons, only same-server requests are allowed).
SOAP, web services
A popular use of AJAX is to implement web services using SOAP, which is an XML-based protocol allowing to access remote
objects running on a server, executing methods on these objects, and getting the
response all through HTTP. Popular implementations of web services clients (.NET,
ATLAS, ...) enable a very transparent mechanism, using proxies.
However, SOAP is often considered heavy, because it involves an XML envelope, serialization
of objects, parameters, etc... and can often be advantageously replaced by leaner
calls using Plain Old XML (POX) as a communication medium.
ASHX generic HttpHandler
ASP.NET 2.0 has made this even easier with the introduction of the ASHX generic
handler type, which provides the developer with a very lean and easy to use HttpHandler.
The advantages of using a HttpHandler over a common Page (web form, ASPX) are multiple:
The HttpHandler gets the request very early, before it is processed by the framework.
When the request's recipient is a Page, the ASP.NET framework fires many events,
which may or may not be implemented by the Page. The mechanism is relatively long
and complex, and can be avoided, especially if the request doesn't need web controls.
It is especially well adapted for frequent, fast requests, for example to query
the state of a server-side process (completion).
Simple communication
The example demonstrates the use of an ASHX handler to get the server's date and
time, formatted according to a parameter (culture). The parameter is passed to the
server using a query string (GET), which is the easiest way to pass parameters to
a web server using HTTP. However, POST communication is also possible using AJAX,
though it is slightly more complicated to fill the request.
http://www.galasoft.ch/mydotnet/articles/Article2006100601.ashx?culture=de-CH
ASHX creation
The URL above, when received by the server www.galasoft.ch, is sent to a custom
HttpHandler, which is known by its address "mydotnet/articles/Article2006100601.ashx".
When an ASHX generic handler is added to a web application, a basic class implementation
is provided:
<%@ WebHandler Language="C#" Class="Article2006100601" %>
using System;
using System.Web;
public class Article2006100601 : IHttpHandler
{
public void ProcessRequest (HttpContext context)
{
context.Response.ContentType = "text/plain";
context.Response.Write("Hello World");
}
public bool IsReusable
{
get
{
return false;
}
}
}
Default implementation of an ASHX generic handler
The first line maps the request "mydotnet/articles/Article2006100601.ashx?culture=de-CH"
to the class Article2006100601, which implements IHttpHandler. An instance of HttpContext is provided to the ProcessRequest method, which contains all information
about the context of the HttpRequest (query string, form fields, header information,
etc...) and provides a HttpResponse object to write back to the client. The method
will be called by the framework when an appropriate request is received.
The IsReusable property indicates whether another request can
use the IHttpHandler instance.
ASHX implementation
For our simple communication model, it is sufficient to set the IsReusable property
to true, and to implement the ProcessRequest method. To make things a little more
interesting, we will use XML in the Response, instead of plain text. The XML document
sent back to the client has the following form:
<?xml version="1.0" encoding="utf-8" ?>
<response>
<current date="DATE" time="TIME" />
<culture>en-GB</culture>
</response>
XML response
DATE and TIME fields will be replaced with the current date and time, formatted
according to the requested culture (for example en-GB, de-CH, ...). The culture
used will sent back to the client for information. If no culture is specified in
the request, the server's culture will be used. The culture can be set using the
web.config file for the application, for example.
If an error occurs, an additional field "error" contains the exception's message.
To create the XML output, we use a XmlDocument. To simplify the code, the tags' and attributes'
names are hard coded.
Code extracts
// Create XML document and its root node
XmlDocument docResponse = new XmlDocument();
XmlElement elResponse = docResponse.CreateElement( "response" );
docResponse.AppendChild( elResponse );
Creating the XMl document and the root
The XML document is created and the root is appended to it. This is a standard XML
way to create a document.
The rest of the code is enclosed in a try...catch block, in order to handle errors
and pass them back to the client.
// Get parameter
string strCulture;
if ( context.Request.QueryString != null
&& context.Request.QueryString[ "culture" ] != null )
{
strCulture = context.Request.QueryString[ "culture" ];
if ( strCulture.Length > 0 )
{
// A culture info is requested --> set it in the current thread
CultureInfo oCultureInfo = new CultureInfo( strCulture );
Thread.CurrentThread.CurrentCulture = oCultureInfo;
Thread.CurrentThread.CurrentUICulture = oCultureInfo;
}
}
else
{
// Get the current culture's name to inform the client
strCulture = Thread.CurrentThread.CurrentCulture.Name;
}
Getting the culture parameter and setting it in the current thread
The next step is to fill the XML document with the corresponding information. We
first create the "current" node, with "date" and "time" attribute. Since we set
the current thread's culture according to the parameter, the corresponding formats
(and language) will be used.
XmlElement elCurrent = docResponse.CreateElement( "current" );
string strDate = DateTime.Now.ToLongDateString();
string strTime = DateTime.Now.ToLongTimeString();
elCurrent.SetAttribute( "date", strDate );
elCurrent.SetAttribute( "time", strTime );
elResponse.AppendChild( elCurrent );
After creating the last node, we set the status code to 200, which is the code for
"OK".
XmlElement elCulture = docResponse.CreateElement( "culture" );
XmlText nText = docResponse.CreateTextNode( strCulture );
elCurrent.AppendChild( nText );
elResponse.AppendChild( elCurrent );
// Status code "OK"
context.Response.StatusCode = 200;
Creating the "culture" element and setting the status code
If an Exception is caught (for example if a non-existing culture name is used as
parameter), the error message is passed to the client in the response. Additionally,
the status code is set to 400, which is the code for a bad request:
catch ( Exception ex )
{
XmlElement elError = docResponse.CreateElement( "error" );
XmlText nErrorMessage = docResponse.CreateTextNode( ex.Message );
elError.AppendChild( nErrorMessage );
elResponse.AppendChild( elError );
// Status code "Bad request"
context.Response.StatusCode = 400;
}
Error handling, setting the status code
As last operation, a "finally" block is executed, in order to set the response's
content type to XML, and to save the XML file to the response's stream. To save
the XML document, we act exactly as we would do to save it to a text file, but instead
of using a file stream, we use the response's OutputStream property.
finally
{
// Set the response's content type. It must be XML!
context.Response.ContentType = "text/xml;charset=utf-8";
// Save the XML document using the response's stream. This concludes the transaction
docResponse.Save(
new XmlTextWriter( context.Response.OutputStream,
context.Request.ContentEncoding ) );
}
Testing the server side code
Now that the server-side code is implemented, it can be called by any client supporting
a web request, for example a web browser. The simplest way to test the code is to
use a web browser and to enter the following URLs:
To test your own implementation, you must of course point the web browser to localhost,
or to your own web server.
The first URL should return the current date and time formatted according to the
server's culture. The second URL should return the current date and time in italian
culture. Finally, the last URL causes an error on the server, because "it-IH" doesn't
exist. The error is caught and the message is passed to the client. Note that the
error is returned in the server's language. On my server, the culture is set to
de-CH, which is swiss german culture. If the culture is set successfully to italian,
then the messages are returned in italian.
In my next article, I will detail the implementation of a JavaScript client running
in a web browser for this server component.
Resources