Calling WebServices via AJAX Part 1
Several months ago, one of my customers asked me a fairly pointed question: how do you call web services from JavaScript? At least, that's what they asked me, but not really what they meant (I'll explain what their real question was in a subsequent post).
Consuming Server-Side Data Using JavaScript
In the old days, you probably wrote a web service that looks like this.
using System;
using System.Web.Services;
[WebService(Namespace = "https://tempuri.org/")]
public class Service : System.Web.Services.WebService
{
[WebMethod]
public string HelloWorld()
{
return "Hello World";
}
}
About 6 years ago, you might have written JavaScript code to call that web service directly using the XMLHTTPRequest ActiveX object. Let's take a look at the way things used to be, referencing my earlier post on a nostalgic look at using XMLHTTPRequest directly from JavaScript to call web services.
var xmlhttp;
function on_click()
{
var xmlToSend = "<?xml version='1.0' encoding='utf-8'?>";
xmlToSend += "<soap:Envelope xmlns:xsi='https://www.w3.org/2001/XMLSchema-instance' ";
xmlToSend += "xmlns:xsd='https://www.w3.org/2001/XMLSchema' ";
xmlToSend += "xmlns:soap='https://schemas.xmlsoap.org/soap/envelope/'>";
xmlToSend += "<soap:Body><HelloWorld xmlns='https://tempuri.org/' />";
xmlToSend += "</soap:Body></soap:Envelope>";
var xmldoc = new ActiveXObject("Microsoft.XMLDOM");
xmldoc.loadXML(xmlToSend);
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
xmlhttp.onreadystatechange = state_Change;
xmlhttp.open("POST", "https://intranet.litwareinc.com/xdomain/WebService.asmx", false);
xmlhttp.setRequestHeader ("SOAPAction", "https://tempuri.org/HelloWorld");
xmlhttp.setRequestHeader ("Content-Type", "text/xml");
xmlhttp.send(xmldoc);
alert(xmlhttp.responseXML.xml);
}
function state_Change()
{
// if xmlhttp shows "loaded"
if (xmlhttp.readyState==4)
{
// if "OK"
if (xmlhttp.status==200)
{
alert("OK");
}
else
{
alert("Problem retrieving XML data");
}
}
}
Wow, no wonder this customer had a hesitation to calling SOAP web services from JavaScript... that's some ugly code, and I sure don't want to have to write that ugliness each time I call a web service.
It goes without saying that this was revolutionary just 6 or 7 years ago, but we've learned enough since then to know that you just don't really want to do this anymore. First, there's not a full SOAP framework in JavaScript, meaning that the serialization/deserialization is going to be left up to you as is the SOAP fault handling. There might be a library somewhere that provides these capabilities, but honestly ask yourself if this is the best approach. How much perf overhead are you wasting with all that string parsing into a DOM representation? Just because you can, doesn't mean you should.
Some of you are probably thinking, "whaddya mean, the way things used to be? I just coded that exact code this week?!?"
Enabling ASMX With JSON
Instead of focusing on XML and SOAP in JavaScript, the world has come to realize that JavaScript Object Notation, or JSON, is much more appropriate for these scenarios. My elaborate XML above becomes something much simpler, the HTTP body of the request simply consisting of:
{"d":"Hello World"}
ASP.NET 3.5 includes AJAX capabilities that make JavaScript programming much, much easier to work with by enabling ASMX to be able to speak both SOAP/XML as well as JSON. To do this, we'll make one tiny change to the service.
using System;
using System.Web.Services;
using System.Web.Script.Services;
[WebService(Namespace = "https://tempuri.org/")]
[ScriptService]
public class Service : System.Web.Services.WebService
{
[WebMethod]
public string HelloWorld()
{
return "Hello World";
}
}
Notice the addition of the ScriptService attribute on our service. This is an attribute new to ASP.NET 3.5, found in the System.Web.Script.Services namespace. It enables an ASMX service to be exposed using either SOAP or JSON over HTTP. Adding this attribute enables ASMX to automatically emit a JavaScript proxy type just by appending "/js" to the querystring. For instance, if my service is in a file called "service.asmx", I would enter "service.asmx/js" into the browser and would be prompted to save something. Save to a file and open that file in Notepad to take a peak at what was generated.
var Service=function() {
Service.initializeBase(this);
this._timeout = 0;
this._userContext = null;
this._succeeded = null;
this._failed = null;
}
Service.prototype={
_get_path:function() {
var p = this.get_path();
if (p) return p;
else return Service._staticInstance.get_path();},
HelloWorld:function(succeededCallback, failedCallback, userContext) {
return this._invoke(this._get_path(), 'HelloWorld',false,{},succeededCallback,failedCallback,userContext); }}
Service.registerClass('Service',Sys.Net.WebServiceProxy);
Service._staticInstance = new Service();
Service.set_path = function(value) { Service._staticInstance.set_path(value); }
Service.get_path = function() { return Service._staticInstance.get_path(); }
Service.set_timeout = function(value) { Service._staticInstance.set_timeout(value); }
Service.get_timeout = function() { return Service._staticInstance.get_timeout(); }
Service.set_defaultUserContext = function(value) { Service._staticInstance.set_defaultUserContext(value); }
Service.get_defaultUserContext = function() { return Service._staticInstance.get_defaultUserContext(); }
Service.set_defaultSucceededCallback = function(value) { Service._staticInstance.set_defaultSucceededCallback(value); }
Service.get_defaultSucceededCallback = function() { return Service._staticInstance.get_defaultSucceededCallback(); }
Service.set_defaultFailedCallback = function(value) { Service._staticInstance.set_defaultFailedCallback(value); }
Service.get_defaultFailedCallback = function() { return Service._staticInstance.get_defaultFailedCallback(); }
Service.set_path("/WebSite7/Service.asmx");
Service.HelloWorld= function(onSuccess,onFailed,userContext) {Service._staticInstance.HelloWorld(onSuccess,onFailed,userContext); }
This might look like gibberish a bit, but this is actually some really, really useful stuff that makes calling web services from JavaScript simple. This code represents a JavaScript proxy that makes it simple to call your service using ASP.NET 3.5. Notice the line:
Service.registerClass('Service',Sys.Net.WebServiceProxy);
This enables our JavaScript class to derive from WebServiceProxy and add new capabilities to it, such as a new method called HelloWorld (the last line in generated proxy). If you are using ASP.NET 3.5, you should be blissfully ignorant that this stuff exists, since the ScriptManager control will automagically handle all of this stuff for you (more on that later). However, if you are using something that isn't ASP.NET, for instance ColdFusion or PHP, you can still use ASP.NET AJAX Library.
I copied the generated proxy above into a JavaScript class called "proxy.js". Since I can leverage ASP.NET 3.5, look at how simple this becomes... here's the client-side code in its entirety.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="https://www.w3.org/1999/xhtml">
<head>
<title>Calling a Service</title>
<script src="MicrosoftAjax.js"type="text/javascript" language="javascript"></script>
<script src="proxy.js" type="text/javascript" language="javascript"></script>
<script language="javascript" type="text/javascript">
function onClick()
{
var proxy = Service.HelloWorld(onSuccess, onFailure);
}
function onSuccess(sender, e)
{
alert(sender);
}
function onFailure(sender, e)
{
alert("Problem retrieving XML data");
}
</script>
</head>
<body>
<div>
<input type="button" onclick="onClick();return false;" />
</div>
</body>
</html>
Look at that... 3 lines of client-side JavaScript code. Notice there are no runat="server" tags at all, it's just a pure HTML page with some JavaScript, there's absolutely no server-side anything to this. The astutue reader might notice the script references and think, "Oh yeah? Where'd the MicrosoftAjax.js script stuff come from?" That's what ships with ASP.NET 3.5, but you can download it and use the client side JavaScript library separately without using ASP.NET at all. You can find out more from the Installing ASP.NET AJAX Documentation:
Download the Microsoft AJAX Library 3.5 from the ASP.NET AJAX Downloads Web site.
Unzip the MicrosoftAJAXLibrary.zip package.
Copy the JavaScript files to the Web site.
It makes total sense, since it's really just a bunch of JavaScript. That means that you can use the ASP.NET AJAX capabilities from JSP, ColdFusion, or even PHP. Some folks even created a CodePlex project to make it even easier to use the ASP.NET AJAX libraries from PHP.
Using the ScriptManager Control
One of the things that ASP.NET makes simpler for you is the ability to skip the step above where you copy the proxy code (recall that I copied into a file called proxy.js)... ASP.NET AJAX will handle the proxy stuff for you if you use a very useful control called ScriptManager. Further, you don't need to pull in the script reference for MicrosoftAjax.js, because the ScriptManager will do that for you, as well. Using ASP.NET, the code simply becomes:
<%@ Page Language="C#"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="https://www.w3.org/1999/xhtml">
<head runat="server">
<title>Calling a Service</title>
<script language="javascript" type="text/javascript">
function onClick()
{
var proxy = Service.HelloWorld(onSuccess, onFailure);
}
function onSuccess(sender, e)
{
alert(sender);
}
function onFailure(sender, e)
{
alert("Problem retrieving XML data");
}
</script>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" >
<Services>
<asp:ServiceReference Path="Service.asmx" />
</Services>
</asp:ScriptManager>
<div>
<input type="button" onclick="onClick();return false;" />
</div>
</form>
</body>
</html>
Next up... replacing our ASMX with WCF.
For more information on ASP.NET AJAX and web services:
PHP for Microsoft AJAX Library
Calling Web Services from Client Script in ASP.NET AJAX
Video: ASP.NET AJAX, XML and Web Services (with a little Virtual Earth)
Calling Web Services using Client-Side ASP.NET AJAX for Server-Side Validation
[Update: d'oh! Thanks to "editor" for noting my CopyPasteException where I left in a stray runat="server" where it shouldn't have been. Great catch. - ed.]
Comments
Anonymous
April 08, 2008
Calling ASMX Web Service from JavaScriptAnonymous
April 08, 2008
The comment has been removedAnonymous
April 08, 2008
Great stuff and all, but how did this make it to the front page of Digg? I thought Digg was losing it's geek only status. Anyway good stuff and congrats on making it to the Digg FP Dan Http://Peakracer.comAnonymous
April 08, 2008
What's with the "D" property in MS AJAX? I rolled some of my own responses with ASP.NET MVC (and therefore did not use "ScriptService" attribute), and the MS AJAX would fail without a "D" property in my JSON response. I have found no documentation about it anywhere ... but I see you mentioned it, indirectly, in your post. What can you tell us about it? BTW, great post.Anonymous
April 08, 2008
rnAt some time in your life and that time may be soon, you will need to use javascript to interactAnonymous
April 08, 2008
This is great, until now I've been using a special Javascript library to call SOAP methods but it didn't always work.Anonymous
April 08, 2008
If you aren't already using the ASP.NET AJAX framework for something else, you don't necessarily need to load the page down with MicrosoftAjax.js and the generated proxy. As long as you POST the request and set the content-type correctly, you can call the JSON serialized version of an ASMX service with any framework (or XHR directly). Here's an example of using jQuery: http://encosia.com/2008/03/27/using-jquery-to-consume-aspnet-json-web-services/Anonymous
April 08, 2008
@Matt - The {d} prefix is part of the expected response for the ASP.NET AJAX library. The responses are wrapped in this "d" object to prevent a known attack which could happen if the type of the response were arrays (array object constructor hijacking).Anonymous
April 08, 2008
Note: just remove the only lasting runat="server" tag from the example in which you manually load generated "proxy.js" file and all would be set and done :) We call it CopyPasteException ;)Anonymous
April 08, 2008
Without using any ASP or add-ons you can make your life easier when calling web services using JavaScript. Use WSRequest.js native JavaScript web service client available at https://wso2.org/repos/wso2/trunk/wsf/javascript/native/. It supports WS addressing stuff and more. This script is currently maintain by former Microsoft employee Jonthan Marsh (now with WSO2).Anonymous
April 09, 2008
We are accepting beta testers for SalesPeriod currently.... Send information through Ajax to your PC almost instantly...and 100% securely. Use our Ajax-enabled web service or whatever you'd like. Thanks. JoeAnonymous
April 09, 2008
Looks like this is an interesting topic to a lot of people since part 1 of this series made it to theAnonymous
April 10, 2008
It is lots simpler to just use Ajax to call a proxy written C# which does all the interaction with the Web Service. Placing everying on the client like this seems to be taking quite a few steps backwards.Anonymous
April 10, 2008
@Clarke - the AJAX solution still needs to be able to call the proxy. In fact, this is what this post series is leading up to... a proxy that "speaks" JSON that composes several other SOAP-based web services. At some point, though, you have to call the proxy, and this is what the code in this article showed how to do. Check out part 2 at http://blogs.msdn.com/kaevans/archive/2008/04/09/calling-web-services-via-ajax-part-2.aspx, and stay tuned for more installments in this series.Anonymous
April 18, 2008
I never taught of JSON before reading this post. Learned some thing new. thank you.Anonymous
April 22, 2008
I hope I'll be able to write such beautiful (and working) code. For now I just keep it to php!