XmlHttp Service Interop - Part 1 (Simple Service Creation)
This entry is the first is a series on XmlHttp Service Interop.
In my day job I am constantly making diverse systems communicate. I make classic ASP talk to the .NET 2.0 Framework, .NET systems talk with Java systems, a Pascal-based system communicate with a custom .NET mail server, and even make .NET components access systems from the stone age. I love interop, but I'm finding that many people don't. From what I see, it seems to be a lack of understanding more so than a lack of desire. It's actually some pretty cool stuff and you can find great books on various interop topics.
While I do work a lot with COM, ES, and web service interop, my favorite communication mechanism is via straight XmlHttp calls. It's just so easy to do and support for it is just about universal. You can take JavaScript and make a call to ASP.NET, go to a VBS WScript and make a call to a web service, or force the oldest, nastiest product in your company to communicate with WS-* services. In this part of the series, we are going to discuss XmlHttp in general and see a call from JavaScript to a manually created service endpoint.
To start off with lets make it clear what we are and are not talking about. We are not talking about sockets or direct TCP calls nor are we talking about a service framework. XmlHttp is a way to transfer XML over HTTP. However, even though we talk about XML, in your using XmlHttp you'll find that this isn't a requirement at all, so, at root what we are doing is making simple HTTP calls.
To see what we're talking about in action, lets create a simple XML service endpoint that accepts a well-defined XML format to allow the sending of e-mail. Then, lets access the service via JavaScript. This is very similar to something I recently created to allow a very old system to send e-mails using the .NET Framework. Of course, in that situation I used the programming language of the system in question (Pascal), not JavaScript.
To begin with lets create the client. I know it seems a bit backwards, but lets look at this from the standpoint of a framework designer: look at how it will be used first, then implement mechanics. Now, the first thing we need to do this is a simple "htm" document. I want the page to be "htm" for the sake of this demonstration, simply to show that there is no server-side processing at all in this page.
Next, we need a way to access our endpoint. I'm not going to get into severe detail about how to do this in every single browser in the world, but, rather, I'm only going to show the standardized way. You can quickly do a search online to see how to extent this behavior to IE5 and IE6.
Skipping the lame setup and stuff many 6th graders can do, let's get right to the core of what we are going to do. The full implementation of everything seen here is in an accompanying VS2005 solution. It would probably be a good idea to have that open as you go through this.
To send a request to a server, simply use syntax similar to the following:
var xmlhttp = new XMLHttpRequest( ); xmlhttp.open('POST', ' Service.aspx', true); xmlhttp.onreadystatechange = function ( ) { if(xmlhttp.readyState == 4) { alert(xmlhttp.responseText); } }; xmlhttp.send(data);
This syntax works in any version of Firefox, the newer versions of Opera, and IE7 (or what I like to call " IE6.5"). Basically, what's happening here is this: you are creating a new instance of an HTTP requestor, giving it some connection information, setting a callback function, and sending some data to the service.
The part that you should look at closely is the XMLHttpRequest::open (note: my double colon syntax is from C++ and simply means Class::Member)function. This obviously takes three parameters: the HTTP method, the HTTP endpoint, and a boolean stating asynchronous communication. I want this to be an asynchronous call, so I'm setting the third parameter to true. I'll come back to the HTTP method in a moment and the HTTP endpoint is just the service address.
After that we see that the property XMLHttpRequest::onreadystatechange is being assigned a JavaScript anonymous function. If you are unfamiliar with these, just think of them as anonymous delegates in C# 2.0. This is the function that's going to be called when the state of the XmlHttp call changed. When inside this function, there are a few parameters you can look at when this function gets calls, but here I'm only testing one: readyState. This property basically states the status of the call. Notice the XMLHttpRequest property is called "onreadystatechange", not "oncomplete". This function is actually called when the state of the HTTP request changes. When I test for readyState == 4 I'm looking for round trip completion. Frankly, you probably never touch the values 1, 2, and 3 though you could check for 0, which means that the XMLHttpRequest::open function has not yet been called. In this situation, if the readyState is 4, then I want to display a message box showing the response content, which is accessible via XMLHttpRequest::responseText. One other very important property you will definately be using a lot is the XMLHttpRequest::status. This property gives values like 404, 415, 500, and so on. If the request did a successful round trip the status will be 200, so that's something you'll probably be testing for quite a bit.
Finally we see the XMLHttpRequest::send method. This simply sends a set of data to the service... well, kind of. In the XMLHttpRequest::open, the first parameter, the HTTP method, is very important. Depending on what you want to do you will either set it to GET or POST. If you are calling a pre-existing page that has no idea what a HTTP stream is, but knows about querystrings, then you will want to use GET. In this situation, you will want to put parameters in the querystring in the HTTP end point, that is, in the second parameter of XMLHttpRequest::open. However, if you are creating your own service, you may want to use POST instead as using POST makes the development on both the client and service simplier. On the client, you don't pack stuff in the querystring (though you still could) and on the server, you can access the data via a stream rather than via parsing the URL or doing any iteration. As that last sentence implies, by using the POST method you send the data you want to submit to the HTTP endpoint as a parameter of the XMLHttpRequest::send function. For those of you who understand WCF terminology, you can think of the HTTP method as being analogous to the WCF binding and the HTTP endpoint as being analogous to the WCF address. The only thing analogous to the WCF contract is the XML schema you use to create your data stream.
Now, since we are sending the information in the data variable to the service, we need to put something in it. For this service, I'm using the following XML, though it doesn't have to be XML at all.
var data = ''; data += '<Mail>'; data += '<ToAddresses>'; data += '<ToAddress>johndoe@tempuri.org</ToAddress>'; data += '</ToAddresses>'; data += '<CcAddresses>'; data += '</CcAddresses>'; data += '<BccAddresses>'; data += '</BccAddresses>'; data += '<FromAddress>no-reply@tempuri.org</FromAddress>'; data += '<Subject>XmlHttp Service Interop - Part 1</Subject>'; data += '<DateTime>03-08-07 2:26PM'; data += '</DateTime>'; data += '<Body>This is the message body.</Body>'; data += '</Mail>';
Given proper event management in JavaScript, you have everything you need to have a fully functional client. Now onto the server.
As we saw when we looked at the client code, the service endpoint is Service.aspx . To help us focus on the task at hand, we aren't going to do anything fancy like using URL aliasing (aka URL rewriting) to make it look cooler, though in reality you would probably do that.
In the code behind for the Service.aspx, we have code that starts like this:
XmlDocument doc = new XmlDocument( ); doc.Load(Request.InputStream); XmlNode documentRoot = doc.DocumentElement; XmlNode mailRoot = documentRoot.SelectSingleNode("//Mail");
Here what we're doing is creating a new XmlDocument and loading the data streamed from the client into it. Then we are getting the root of the document via XPath. The rest of the code is in the accompanying project and simply consists of a bunch of XPath queries to get the information from the document.
After we found all the values we needed in the XML document, either via XPath or another mechanism, we can do whatever we want with what we found. The important point is this: all we have to do is a simple Response.Write ("") to send data sent back to the client, which in turn changes the readyState in the previously seen JavaScript to 4, thus allowing the client to display the output in the alert window. It's really just as simple as this: the client sends stuff to the service, the service does something and sends stuff back.
Now, we could beef this up a bit by adding some HTTP headers. This is something you may find yourself doing often. To do this, use XMLHttpRequest::setRequestHeader to set a key/value pair on the connection. Here's an example.
xmlhttp.setRequestHeader('X-App-Source', 'My Client');
That 'X-App-Source' was completely made up. You could use 'My Awesome Service Caller' if you wanted. That doesn't matter, what does matter however is that you put this after the call to XMLHttpRequest::open or else you will seriously want to throw something across the room, because it's a painfully subtle error that will cause the call will fail every time.
On the server side, to access a header, simply do this:
String xAppSource = Request.Headers["X-App-Source"];
I know. You were expecting something a bit more profound. Okay, I can satisfy that need. If you have many headers and you want them all, here's what you can do.
foreach (String header in Request.Headers) { // Do whatever you want... }
Whatever you do, try to fight the temptation to do this:
Dictionary<String, String> headers = new Dictionary<String, String>( ); foreach (String header in Request.Headers) { headers.Add(header, Request.Headers [header]); }
As nice as that looks, if you really want to have a headers key/value pair collection, you can just do this:
NameValueCollection headers = Request.Headers;
Regardless of what you do with the headers, remember that they are there for a reason and if you do a lot of service calls, you will find yourself using HTTP headers a lot. This is something you will see in the next part of the series.
So, that's a quick overview of XmlHttp. Please see the solution provided with this post for the full code. The next part of the series discusses making manual XmlHttp calls to WCF.
Materials