Navigate back home
GalaSoft Laurent Bugnion
.NET: A simple AJAX service using Plain Old XML (POX)
Part 1: Server-side implementation

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.
ASHX creation

The URL above, when received by the server, 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.