Section 16.4. Consuming Web Services with JavaScript


16.4. Consuming Web Services with JavaScript

The automatic mechanisms that Atlas provides for accessing web services are really easy to use because they take care of most of the work. However, there are situations when these mechanisms do not work. For instance, imagine that you have to call a (same domain) web service that is not written in .NET, but in another server-side technology such as PHP or Java. Or imagine that you cannot use Atlas for some reason (for instance, due to company policies regarding third-party modules or disagreement with the 331icense). This book is not limited to covering how to use Atlas to write Ajax-empowered ASP.NET applications; it also discusses how to use the underlying technologies involved. So this section covers alternative ways to call remote web services from JavaScript.

Before we go into detail, you have to remember once again that the security model of JavaScript forbids cross-domain scripting. That means that you cannot access remote sites using JavaScript (implicitly using XMLHttpRequest). If you need to call a remote web service, you will have to revert to the Atlas web service bridge covered in the preceding section.

There are two possible ways to call a web service programmatically using JavaScript. You can either bet on XMLHttpRequest, or write a suitable SOAP HTTP request and then evaluate the data returned from the server. This is quite complicated and very error-prone. A much better approach is to use built-in technology or official add-ons to the browsers that solve this task for you.

Unfortunately, the two major browser types, Internet Explorer and Mozilla (including Firefox, Camino, and others), have a completely different approach to calling web services. Therefore, we must now follow divergent paths and cover each of these browsers individually. At the end of this section, we'll join the two different programming models back up to create a more or less single browser-agnostic script.

16.4.1. Web Services and Internet Explorer

Some years ago, Microsoft started working on a set of script code to make calling web services from within its browser possible. Basically, the code instantiates XMLHttpRequest, sets the required HTTP headers for a SOAP request, creates the body of the request, waits for the SOAP response and transforms that back into something JavaScript can use. In addition, the code can parse the WSDL description of the web service and generate a local proxy object.

The idea is simple, the implementation is not. The final version of the code (Version 1.0.1.1120), consists of almost 2, 300 lines of code. However, in 2002, Microsoft abandoned the component it had written. This is a pity, especially since the component still works well today. Luckily, the code is still available in the archives of MSDN, at http://msdn.microsoft.com/archive/en-us/samples/internet/behaviors/library/webservice/default.asp.

Download the file webservice.htc and save it in the directory where your example scripts reside. The file extension .htc stands for "HTML control," otherwise known as an Internet Explorer behavior. Using a CSS style supported only in Internet Explorer, you can load the file into your application:

 <div  style="behavior:url(webservice.htc);"></div> 

The name you provide in the id attribute can then be used in JavaScript to access both the HTML control and the web service it is linked to.

This "linking" can be achieved by providing the WSDL description of the web services you want to use. The method of the .htc file you need for this task is useService(). You also have to provide a unique identifier to access the specific web service later on:

 WebService.useService("MathService.asmx?WSDL", "MathService"); 

Then, you call the web service. However, the order of the parameters of the associated method, callService(), is a bit different from the proxy object Atlas is creating. These parameters are required:

  • A reference to the callback method

  • The name of the web method to be called

  • The parameter(s) to be submitted to the web service

Error handling, by the way, is not supported (unlike with Atlas where exception information is provided to the client script).

In the case of the MathService web service, the following call executes the division:

 WebService.MathService.callService(   callComplete,   "DivideNumbers",   6, 7); 

The callback function then gets the result as an object whose value attribute contains the return value of the web service:

 function callComplete(result) {   document.getElementsById("c").innerHTML = result.value; } 

Example 16-7 shows the complete code for this example.

Example 16-7. Calling a web service from Internet Explorer

 MathServiceInternetExplorer.htm <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head>   <title>Atlas</title>   <script language="Javascript" type="text/javascript">   function callService(f) {     document.getElementById("c").innerHTML = "";     WebService.useService("MathService.asmx?WSDL", "MathService");     WebService.MathService.callService(       callComplete,       "DivideNumbers",       f.elements["a"].value, f.elements["b"].value);   }   function callComplete(result) {     document.getElementById("c").innerHTML = result.value;   }   </script> </head> <body>   <div  style="behavior:url(webservice.htc);">   </div>   <form method="post" onsubmit="return false;">     <div>       <nobr>         <input type="text"  name="a" size="2" />         :         <input type="text"  name="b" size="2" />         =         <span  style="width: 50px;"></span>       </nobr>       <br />       <input type="button" value="Divide Numbers" onclick="callService(this.form);" />     </div>   </form> </body> </html> 

You will get some very strange errors if you do not place the web service behavior at the beginning of the <body> element, including error messages claiming that WebService is not defined (although a window.alert(WebService) call works).


16.4.2. Web Services and Mozilla Browsers

Relatively recent versions of Mozilla browsers also contain support for web services as a built-in extension to the browser. Unfortunately, the component for handling web services does not seem to have received much attention recently from the community, but at least it does its job well. However it is virtually undocumented, and you'll find a lot of strange advice on how 334o make it work. The approach we'll use in this section does the job, but involves quite a bit of extra code.

Mozilla's SOAPCall class takes care of all communication with a remote service. Since it uses SOAP 1.1, you have to set the SOAPAction header (which, conveniently, is a property of the SOAPCall class) and the URL of the web service's file. Here's code to do it for the purposes of our example:

 var soapcall = new SOAPCall(); soapcall.actionURI = "http://hauser-wenz.de/atlas/DivideNumbers"; soapcall.transportURI = "http://localhost:1234/Atlas/MathServiceDocEnc.asmx"; 

The value of the transportURI property must be an absolute URL. So make sure you change the URI (if using the development server of Visual Studio/Visual Web Developer, especially the port number) to your local system.


All parameters that you provide to the web service are of type SOAPParameter. In the class constructor, you provide first the value of the parameter, then its name:

 var p1 = new SOAPParameter(6, "a"); var p2 = new SOAPParameter(7, "b"); 

Now comes the tricky part. If you omit the next step, the SOAP call is sent (and also the returned values are received), but on the server, the service receives only empty parameters. In the case of our division calculation, this leads to a "divide by zero" exception, but this time, an unwanted one.

The trick is to manually set the correct encoding for the integer values. To do so, you have to load the appropriate namespaces for the SOAP integer data type. Then, you set the schemaType property of the parameters you want to send to the web service to the generated data type. Here's the code to complete those steps:

 var senc = new SOAPEncoding(); assenc = senc.getAssociatedEncoding(   "http://schemas.xmlsoap.org/soap/encoding/",   false); var scoll = assenc.schemaCollection; var stype = scoll.getType(   "integer",   "http://www.w3.org/2001/XMLSchema"); p1.schemaType = stype; p2.schemaType = stype; 

Now, you have to assemble the web service call. The encode() method takes care of that, but only after you have provided several parameters, as shown in the following snippet:

 soapcall.encode(   0,                               //default value for SOAP version 1.1   "DivideNumbers",                 //name of web method   "http://hauser-wenz.de/atlas/",  //Namespace   0,                               //number of additional headers   new Array(),                     //additional headers   2,                               //number of parameters   new Array(p1, p2)                //parameters ); 

Finally, you need to asynchronously invoke the web service using the asyncInvoke() method. As a parameter you must provide a reference to the callback function:

 soapcall.asyncInvoke(callComplete); 

The callback function receives three parameters:

  • The XML resulting from the web service call

  • The SOAPCall object (in case you are interested in its SOAP headers)

  • The HTTP status code of the call

Now, the only remaining problem is to extract the information you need from the returned XML. So let's have a look at a sample of the XML that is returned from a call to MathService (data you can retrieve using software like the Windows tool Fiddlerhttp://www.fiddlertool.com/fiddleror the Mozilla extension Live HTTP headers http://livehttpheaders.mozdev.org):

 <?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap .org/soap/envelope/">   <soap:Body>     <DivideNumbersResponse xmlns="http://hauser-wenz.de/atlas/">       <DivideNumbersResult>0.857142866</DivideNumbersResult>     </DivideNumbersResponse>   </soap:Body> </soap:Envelope> 

Working from the representation of the XML data, we can see that the following steps are required to access the actual return value, 0.857142866:

  • Use the property body to get access to the <soap:Body> element.

  • Use the property firstChild to access the <DivideNumberResponse> element.

  • Use firstChild again to access the <DivideNumbersResult> element.

  • Use a third firstChild reference to access the text node under the <DivideNumbersResult> element.

  • Use the data property to access the text within the text node.

Here's the JavaScript code you need to retrieve the result of the web service call:

 function callComplete(result, soapcall, status) {   document.getElementById("c").innerHTML =     result.body.firstChild.firstChild.firstChild.data; } 

Putting all of these elements together, you get the code shown in Example 16-8.

Example 16-8. Calling a web service in Mozilla browsers

 MathServiceMozilla.htm <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head>   <title>Atlas</title>   <script language="Javascript" type="text/javascript">   function callService(f) {     document.getElementById("c").innerHTML = "";     var soapcall = new SOAPCall();     soapcall.actionURI = "http://hauser-wenz.de/atlas/DivideNumbers";     soapcall.transportURI = "http://localhost:1234/Atlas/MathService.asmx";     var p1 = new SOAPParameter(parseInt(f.elements["a"].value), "a");     var p2 = new SOAPParameter(parseInt(f.elements["b"].value), "b");     var senc = new SOAPEncoding();     assenc = senc.getAssociatedEncoding(       "http://schemas.xmlsoap.org/soap/encoding/",       false);     var scoll = assenc.schemaCollection;     var stype = scoll.getType(       "integer",       "http://www.w3.org/2001/XMLSchema");     p1.schemaType = stype;     p2.schemaType = stype;     soapcall.encode(       0,                               //default value for SOAP version 1.1       "DivideNumbers",                 //name of web method       "http://hauser-wenz.de/atlas/",  //Namespace       0,                               //number of additional headers       new Array(),                     //additional headers       2,                               //number of parameters       new Array(p1, p2)                //parameters     );     soapcall.asyncInvoke(callComplete);   }   function callComplete(result, soapcall, status) {     document.getElementById("c").innerHTML =       result.body.firstChild.firstChild.firstChild.data;   }   </script> </head> <body>   <form method="post" onsubmit="return false;">     <div>       <nobr>         <input type="text"  name="a" size="2" />         :         <input type="text"  name="b" size="2" />         =         <span  style="width: 50px;"></span>       </nobr>       <br />       <input type="button" value="Divide Numbers" onclick="callService(this.form);" />     </div>   </form> </body> </html> 

Remote Web Services with Mozilla

The Mozilla security model does allow you to call remote services. However, the script has to ask the user for additional privileges (see Figure 16-7). The specific privilege required in this case is UniversalBrowserRead, meaning that the browser may read from anywhere (including remote servers and the local filesystem).

Figure 16-7. Firefox requests additional privileges to call the remote service


 netscape.security.PrivilegeManager.enablePrivilege(   "UniversalBrowserRead"); 

However, the default configuration of Mozilla, Firefox, and other browsers only grants this privilege for local files, so this approach is basically applicable only to intranet applications. Figure 16-7 shows the message that users of Mozilla browsers see when these elevated privileges are requested.


16.4.3. Web Services with Both Browsers

To wrap up our look at techniques for accessing web services using JavaScript in either Internet Explorer or in the Mozilla family of browsers, let's combine both approaches in a single page. To do this, you first have to decide how to do the browser detection. As discussed in Chapter 2, the best 338ay of doing so is to check for browser capabilities, not for browser types. In Example 16-9, let's use the approach that worked for us in Chapter 2 where you learned how to create the XMLHttpRequest object. The idea is that we just try to create one of the browser-specific objects. If that succeeds, we continue as planned. If it fails, we use a method that works in the other browser. We'll use two nested TRy...catch constructs to make the calls.

Example 16-9 shows the complete markup and script needed to do the job. Be sure that you test this code in different browsers, and remember to set the soapcall.transportURI property to the URL of the site (and, if required, port) that you are using.

Example 16-9. Calling a web service in either Internet Explorer or Mozilla

 MathService.htm <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head>   <title>Atlas</title>   <script language="Javascript" type="text/javascript">   function callService(f) {     document.getElementById("c").innerHTML = "";     try {       WebService.useService("MathService.asmx?WSDL", "MathService");       WebService.MathService.callService(         callComplete,         "DivideNumbers",         parseInt(f.elements["a"].value), parseInt(f.elements["b"].value));     } catch (e) {       try {         var soapcall = new SOAPCall();         soapcall.actionURI = "http://hauser-wenz.de/atlas/DivideNumbers";         soapcall.transportURI = "http://localhost:1234/Atlas/MathService.asmx";         var p1 = new SOAPParameter(parseInt(f.elements["a"].value), "a");         var p2 = new SOAPParameter(parseInt(f.elements["b"].value), "b");         var senc = new SOAPEncoding();         assenc = senc.getAssociatedEncoding(           "http://schemas.xmlsoap.org/soap/encoding/",           false);         var scoll = assenc.schemaCollection;         var stype = scoll.getType(           "integer",           "http://www.w3.org/2001/XMLSchema");         p1.schemaType = stype;         p2.schemaType = stype;         soapcall.encode(           0,                               //default value for SOAP version 1.1           "DivideNumbers",                 //name of web method           "http://hauser-wenz.de/atlas/",  //Namespace           0,                               //number of additional headers           new Array(),                     //additional headers           2,                               //number of parameters           new Array(p1, p2)                //parameters         );         soapcall.asyncInvoke(callComplete);       } catch (e) {     window.alert("Your browser is not supported.");       }     }   }   function callComplete(result, soapcall, status) {     if (result.value != null) {       document.getElementById("c").innerHTML = result.value;     } else {       document.getElementById("c").innerHTML =         result.body.firstChild.firstChild.firstChild.data;     }   }   </script> </head> <body>   <div  style="behavior: url(webservice.htc);">   </div>   <form method="post" onsubmit="return false;">     <div>       <nobr>         <input type="text"  name="a" size="2" />         :         <input type="text"  name="b" size="2" />         = <span  style="width: 50px;" ></span>       </nobr>       <br />       <input type="button" value="Divide Numbers" onclick="callService(this.form);" />     </div>   </form> </body> </html> 

As you can see in Figures 16-1 and 16-9, Example 16-9 works in both major browser types.

Figure 16-8. The script now works in Internet Explorer


Figure 16-9. The script also works in Mozilla browsers like Firefox


All that remains is to reflect on whether it is all worth itdo you really want to use a browser-specific approach to call a web service? Web sites whose server platform is ASP.NET can stick with Atlas. Since Atlas is easy to deploy and also available with a Go Live license, the approach taken in the final section should be seen as a last resort only, especially since development of the Mozilla web service functionality is obviously stalled.




Programming Atlas
Programming Atlas
ISBN: 0596526725
EAN: 2147483647
Year: 2006
Pages: 146

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net