XML Files

Merging XML Files, Schema Validation, and More

Aaron Skonnard

Code download available at:XMLFiles0303.exe(174 KB)

Q How can I merge the contents of multiple XML files into one?

Q How can I merge the contents of multiple XML files into one?

A There are a variety of ways to accomplish this with the Microsoft® .NET Framework, each of which uses a different type of XML API. To do this with the DOM API, you need to use the ImportNode function for copying XmlNode objects between documents. Since ImportNode only transfers control of the object between documents, you still need to call AppendChild (or InsertBefore, and so forth) in order to inject the node into the tree.

A There are a variety of ways to accomplish this with the Microsoft® .NET Framework, each of which uses a different type of XML API. To do this with the DOM API, you need to use the ImportNode function for copying XmlNode objects between documents. Since ImportNode only transfers control of the object between documents, you still need to call AppendChild (or InsertBefore, and so forth) in order to inject the node into the tree.

Figure 1 shows a complete C# example that merges two XML documents (product.xml and material.xml) into one. The new document has a root Container element which will hold the contents of the other two files. Use the XmlReader and XmlWriter APIs. To do this you'd use a single XmlWriter to generate the new XML document, along with several XmlReaders to merge other documents into the new one. XmlWriter provides a WriteNode method that reads through the supplied XmlReader object and writes all of its nodes in one shot (see Figure 2 ).

Figure 2 WriteNode Method

// new document stream XmlWriter tw = new XmlTextWriter(Console.Out); tw.WriteStartDocument(); tw.WriteStartElement("Container"); // write product.xml to new document stream XmlTextReader reader = new XmlTextReader("product.xml"); tw.WriteNode(reader, true); // write material.xml to new document stream reader = new XmlTextReader("material.xml"); tw.WriteNode(reader, true); tw.WriteEndElement(); tw.WriteEndDocument();

Figure 1 Merging XML Docs

// the other documents will be merged into this one // under the Container element XmlDocument doc = new XmlDocument(); XmlNode cont = doc.CreateElement("Container"); doc.AppendChild(cont); // first document to merge XmlDocument stuff = new XmlDocument(); stuff.Load("product.xml"); XmlNode imported = doc.ImportNode(stuff.DocumentElement, true); doc.DocumentElement.AppendChild(imported); // second document to merge stuff.Load("material.xml"); imported = doc.ImportNode(stuff.DocumentElement, true); doc.DocumentElement.AppendChild(imported); // print merged documents Console.WriteLine(doc.InnerXml);

And finally, you could also use XSLT to accomplish this via the document function. This is probably the easiest solution of all:

<xsl:transform version="1.0" xmlns:xsl="https://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <Container> <xsl:copy-of select="document('product.xml')"/> <xsl:copy-of select="document('material.xml')"/> </Container> </xsl:template> </xsl:stylesheet>

See the code download at the link at the top of this article.

Q Is it possible to validate an XML document against an XML Schema definition embedded within a Web Services Description Language (WSDL) document?

Q Is it possible to validate an XML document against an XML Schema definition embedded within a Web Services Description Language (WSDL) document?

A Yes. XmlValidatingReader is the class that provides XML Schema validation in the .NET Framework. It provides a Schemas property of type XmlSchemaCollection for holding a cache of schemas you want to validate against:

XmlTextReader r = new XmlTextReader("employee.xml"); XmlValidatingReader vr = new XmlValidatingReader(r); // load schema into cache vr.Schemas.Add("https://example.org/employee", "emp.xsd"); // read through XML stream - validates along the way while (vr.Read()) ; // do nothing

This version of Add requires you to specify the namespace along with the file name. To get around having to specify the namespace, you can generate an XmlSchema object by calling XmlSchema.Read and use that instead:

XmlSchema schema = XmlSchema.Read("emp.xsd", null); vr.Schemas.Add(schema);

A Yes. XmlValidatingReader is the class that provides XML Schema validation in the .NET Framework. It provides a Schemas property of type XmlSchemaCollection for holding a cache of schemas you want to validate against:

XmlTextReader r = new XmlTextReader("employee.xml"); XmlValidatingReader vr = new XmlValidatingReader(r); // load schema into cache vr.Schemas.Add("https://example.org/employee", "emp.xsd"); // read through XML stream - validates along the way while (vr.Read()) ; // do nothing

This version of Add requires you to specify the namespace along with the file name. To get around having to specify the namespace, you can generate an XmlSchema object by calling XmlSchema.Read and use that instead:

XmlSchema schema = XmlSchema.Read("emp.xsd", null); vr.Schemas.Add(schema);

The process for validating against an XML Schema definition embedded in a WSDL definition is very similar. Read the WSDL definition into a ServiceDescription object by calling ServiceDescription. You need to read and then iterate through the internal XmlSchema objects, loading them into the XmlSchemaCollection used by XmlValidatingReader:

ServiceDescription sd = ServiceDescription.Read("emp.wsdl"); foreach(XmlSchema embeddedXsd in sd.Types.Schemas) vr.Schemas.Add(embeddedXsd);

It doesn't really matter where the schema resides as long as it can be parsed into an XmlSchema object.

Q What are the best techniques for managing Web Service references in client applications?

Q What are the best techniques for managing Web Service references in client applications?

A One of the ongoing challenges of Web development is link maintenance. Here's a typical scenario. Site A links to resources offered by site B. Sometime later, site B decides to reorganize its site, but site A never finds out about the changes, so its links remain broken for a (potentially long) period of time.

A One of the ongoing challenges of Web development is link maintenance. Here's a typical scenario. Site A links to resources offered by site B. Sometime later, site B decides to reorganize its site, but site A never finds out about the changes, so its links remain broken for a (potentially long) period of time.

Figure 3 Client with Hardcoded Address

Figure 3** Client with Hardcoded Address **

Web Services face the same issue since clients are usually tightly coupled to their endpoints through hardcoded URLs, as illustrated in Figure 3. For example, consider the following .asmx class that contains a simple WebMethod that echoes the input:

[WebService(Namespace="https://example.org/echo")] public class EchoClass : WebService { [WebMethod] public string Echo(string input) { return String.Format("Endpoint location: {0}\nInput: {1}", HttpContext.Current.Request.Url, input); } }

Assume that this class is bound to an .asmx file called echo.asmx that resides at the URL https://localhost/echoservice/echo.asmx. Using the .NET Framework, you can build a proxy class for this endpoint with wsdl.exe, as shown in the following:

wsdl.exe https://localhost/echoservice/echo.asmx?wsdl

When you do, by default wsdl.exe generates a proxy class with the endpoint's URL hardcoded into the constructor, as shown here:

••• public class EchoClass : SoapHttpClientProtocol { public EchoClass() { this.Url = "https://localhost/echoservice/echo.asmx"; } ••• }

This enables you to write code against the proxy without specifying the endpoint location at run time. When you invoke methods on the class, it automatically generates the correct SOAP messages and sends them to the URL hardcoded in the constructor:

EchoClass e = new EchoClass(); // SOAP message for Echo automatically sent to // https://localhost/echoservice/echo.asmx Console.WriteLine(e.Echo());

However, if the Web Service administrator decides to move endpoint1.asmx to a different location after the client application ships, he has broken the Web Service link, which can only be fixed by recompiling all client apps hardcoded against the original path.

To help alleviate this problem, wsdl.exe makes it possible to read the URL from a user-defined key in the client application's configuration file. This is accomplished by using the /appsettingurlkey:<key> switch, as shown here:

wsdl.exe /appsettingurlkey:EchoServiceLocation https://localhost/echoservice/echo.asmx?wsdl

In this case, the generated proxy's constructor will contain extra code for reading the URL value from the application configuration file before using the default location:

public EchoClass() { string urlSetting = ConfigurationSettings.AppSettings[ "EchoServiceLocation"]; if ((urlSetting != null)) { this.Url = urlSetting; } else { this.Url = "https://localhost/echoservice/echo.asmx"; } }

Now, assuming the application assembly is called client.exe, you can create a configuration file called client.exe.config with the following appSettings key to configure the proxy class dynamically:

<configuration> <appSettings> <add key="EchoServiceLocation" value="https://localhost/echoservice/echo.asmx" /> </appSettings> </configuration>

With this in place, if the Web Service administrators decide to move things around, at least client applications don't have to be recompiled. They can simply update their local configuration files with the new location and they're ready to go. This still isn't ideal, however, since clients have to find out the correct location and make the change manually.

Universal Description, Discovery, and Integration (UDDI) can simplify things a step further through the UDDI invocation pattern. This pattern defines a sequence of steps for determining the current location of a Web Service at run time. In a nutshell it states that if you invoke a service and the endpoint is not found, you then query a well-known UDDI repository (whose address shouldn't change) and request the Web Service's current location. As long as the Web Service is registered with UDDI and has kept its record up to date, the client should then be able to update its local configuration and reattempt the invocation. Implementing the UDDI invocation pattern makes it possible for your proxy to determine the new location and update the local configuration file automatically.

To use the UDDI invocation pattern, first you need to publish your Web Service with a UDDI repository, like the one Microsoft offers at https://uddi.microsoft.com. You could also host your own enterprise-level UDDI repository and just use it locally. Windows® Server 2003 will ship with UDDI Services to facilitate this. It doesn't really matter where you decide to publish your Web Services since the UDDI API is the same regardless.

If you don't already have an account with the UDDI server you decide to use, you'll have to register yourself first. Then, simply walk through the steps to publish your Web Service. Once you've published your Web Service, the UDDI server assigns it a binding key that you'll use in the future to look it up (see Figure 4). With the UDDI SDK 2.0, Microsoft makes it easy to programmatically access the UDDI repository in languages targeted for the .NET Framework. The FindCurrentLocation method in Figure 5 shows how to implement part of the UDDI invocation pattern.

Figure 5 FindCurrentLocation

using System; using System.Configuration; using Microsoft.Uddi; ••• public static string FindCurrentLocation( WebException we, string bindingKeyName, string uddiKeyName) { if (NotFound(we)) { Inquire.Url = ConfigurationSettings.AppSettings[keyName]; GetBindingDetail gbd = new GetBindingDetail(); gbd.BindingKeys.Add( ConfigurationSettings.AppSettings[bindingKeyName]); BindingDetail bd = gbd.Send(); if (bd != null && bd.BindingTemplates.Count > 0) return bd.BindingTemplates[0].AccessPoint.Text; } return ""; }

Figure 4 Registering a Web Service with UDDI

Figure 4** Registering a Web Service with UDDI **

The method expects to find the UDDI server's URL as well as the Web Service binding key in the client application's configuration file, making it easy to reuse the code across projects and Web Services. It only contacts the UDDI server if it determines the WebException was related to an inability to find the host or endpoint through the call to NotFound (see Figure 6).

Figure 6 NotFound

internal static bool NotFound(WebException we) { if (we.Status == WebExceptionStatus.ConnectFailure) return true; else if (we.Response != null) { HttpWebResponse r = we.Response as HttpWebResponse; switch (r.StatusCode) { case HttpStatusCode.Gone: case HttpStatusCode.NotFound: case HttpStatusCode.Moved: case HttpStatusCode.Redirect: return true; default: return false; } } return false; }

If you're successful in finding the new location of the Web Service through the call to FindCurrentLocation, you'll also want to update the client's configuration file so it doesn't have to perform the query every time the Web Service is invoked. This can be accomplished using the DOM, as shown in Figure 7. I put FindCurrentLocation and UpdateLocalConfiguration into a class called InvocationPattern and marked them as public/static. You can easily use these methods inside of your error handling code for a given Web Service invocation.

Figure 7 Update Config File

public static void UpdateLocalConfiguration(string configFileName, string keyName, string keyValue) { XmlDocument doc = new XmlDocument(); doc.Load(configFileName); string query = string.Format( "/configuration/appSettings/add[@key='{0}']", keyName); XmlNode key = doc.SelectSingleNode(query); if (key != null) { XmlElement keyElement = key as XmlElement; keyElement.SetAttribute("value", keyValue); doc.Save(configFileName); } }

Consider a client app that uses the following configuration file:

<configuration> <appSettings> <add key="EchoServiceLocation" value="https://staff.develop.com/aarons/dotnet/echo.asmx" /> <add key="UddiServerLocation" value="https://test.uddi.microsoft.com/inquire" /> <add key="EchoServiceUddiBindingKey" value="c4e334ff-5e16-471a-9886-5e4c8fd29025" /> </appSettings> </configuration>

The client application code in Figure 8 illustrates how to use these methods to implement the UDDI invocation pattern.

Figure 8 Implement UDDI Invocation Pattern

using DevelopMentor.Uddi; ••• EchoClass proxy = new EchoClass(); try { // first attempt, upon failure we'll contact UDDI // below and try again... Console.WriteLine(proxy.Echo(args[0])); } catch(WebException e) { string newLoc = InvocationPattern.FindCurrentLocation( e, "EchoServiceUddiBindingKey", "UddiServerLocation"); if (newLoc != "") { // reattempt invocation w/new location proxy.Url = newLoc; Console.WriteLine(proxy.Echo(args[0])); // update local configuration file InvocationPattern.UpdateLocalConfiguration( "echoclient.exe.config", "EchoServiceLocation", newLoc); return; } Console.WriteLine(e.Message); }

With this code in place the client application can now correct itself when the Web Service location changes over time. You can experiment with this by simply changing the EchoServiceLocation value in the client's configuration file to a bogus URL. It should then contact the UDDI server and update the configuration file with the correct URL. Also, try renaming your .asmx file to something else (echomoved.asmx), update the location in UDDI, and rerun the client. It should automatically invoke the new location and the client configuration file should then contain the new URL. This approach assumes that you're willing to keep the UDDI information up to date and that the UDDI server will always be available at the specified location.

Another solution that completely decouples the client from the physical network architecture is to route incoming Web Service requests to the correct endpoint location at run time. You can use a Web Service router that translates logical endpoints into physical locations (see Figure 9). In this instance the client is configured to call a logical endpoint, the router (whose address shouldn't change), which determines the correct endpoint to use at run time.

Figure 9 SOAP Router

Figure 9** SOAP Router **

Web Services Enhancement 1.0 for Microsoft .NET (WSE) makes this possible through WS-Routing and WS-Referral. To use this approach, you need to install the WSE (see https://msdn.microsoft.com/webservices) and configure a virtual directory to serve as your router. Then you'll choose and configure a new file extension to use for routing purposes (.rp, for example). From here you can configure the virtual directory to use a referral cache file to determine routing instructions at run time. The following code shows a sample referral cache file that forwards all incoming requests targeted at https://localhost/referralrouter/echo.rp to https://localhost/echoservice/echo.asmx:

<r:referrals xmlns:r="https://schemas.xmlsoap.org/ws/2001/10/referral"> <r:ref> <r:for> <r:exact>https://localhost/referralrouter/echo.rp </r:exact> </r:for> <r:if /> <r:go> <r:via>https://localhost/echoservice/echo.asmx </r:via> </r:go> <r:refId>uuid:fa469956-0057-4e77-962a-81c5e292f2ae</r:refId> </r:ref> </r:referrals>

Now, if you decide to move the Web Service to a new location, you can simply update the referral cache file and clients don't have to do a thing—they're automatically forwarded on the next invocation. For more on using routing techniques with the WSE, see "Routing SOAP Messages with Web Services Enhancements 1.0".

As you can see, there are various techniques to managing references to Web Services in client applications. At minimum, don't hardcode the URLs into your proxy classes; use configuration files. Also invest in a long-term maintenance strategy that includes the UDDI invocation pattern or server-side routing techniques.

Feel free to download the sample code from the link at the top of this article and experiment on your own.

Q How can I invoke a Web Service directly from a browser?

Q How can I invoke a Web Service directly from a browser?

A Since Web Services are designed for application consumption (not human consumption), today's Web browsers don't offer much built-in integration. Actually, it's more common for server-side ASP or ASP.NET pages to invoke Web Services, effectively merging information from multiple sources into the HTML stream rendered by the browser. It is possible, however, to invoke Web Services directly from client-side HTML pages, but it does require a bit of client-side script for it to be a useful part of a Web app.

A Since Web Services are designed for application consumption (not human consumption), today's Web browsers don't offer much built-in integration. Actually, it's more common for server-side ASP or ASP.NET pages to invoke Web Services, effectively merging information from multiple sources into the HTML stream rendered by the browser. It is possible, however, to invoke Web Services directly from client-side HTML pages, but it does require a bit of client-side script for it to be a useful part of a Web app.

The ASP.NET Web Service infrastructure facilitates browser integration by supporting simple HTTP invocation protocols that don't require SOAP messages on the wire. For example, you should consider the following WebMethod called Add:

[WebMethod] public double Add(double x, double y) { return x+y; }

By default, ASP.NET makes it possible to invoke this method using three techniques: SOAP, HTTP GET, and HTTP POST. SOAP is the standard technique supported by today's Web Service toolkits and infrastructure. The HTTP GET and POST mechanisms make it much easier to invoke Web Services from dumb HTTP clients (like a Web browser) that know nothing about SOAP.

The following is the HTTP Get request:

GET /mathservice/math.asmx/add?<em xmlns="https://www.w3.org/1999/xhtml">x</em>=<em xmlns="https://www.w3.org/1999/xhtml">string</em>&<em xmlns="https://www.w3.org/1999/xhtml">y</em>=<em xmlns="https://www.w3.org/1999/xhtml">string</em>HTTP/1.1 Host: localhost

In the case of HTTP GET, you simply tack the input parameters onto the query string, as shown here:

https://localhost/mathservice/math.asmx/Add?x=33&y=66

If you're using HTTP POST, the input parameters are encoded the same way, only they're sent as part of the message body, not as part of the query string. Since the input data is expected in the URL-encoded format, you can invoke the Web Service directly from an HTML form. For example, the following form generates the correct HTTP message to invoke Add using a GET request:

<form target="_blank" method="GET" action='https://localhost/mathservice/math.asmx/Add' > <input type="text" size="50" name="x"> <input type="text" size="50" name="y"> </form>

The documentation generated by ASP.NET contains a form (like the one shown in the previous code snippet) for testing the Web Service (see Figure 10). When you press the Invoke button, however, it opens a new instance of Microsoft Internet Explorer and displays the raw XML result. Had the form not used target="_blank", it would have rendered the raw XML result in the same instance of Internet Explorer.

Figure 10 Testing the Web Service

Figure 10** Testing the Web Service **

The HTML form technique does work across all Web browsers, but you wouldn't want the raw XML to be rendered and viewed by the user. Instead, you'd probably want to harvest the returned information and utilize it in your HTML display as you see fit. This is not difficult to do, but it does require writing a bit of client-side scripting to manually send the request, harvest the result, and update the page. And depending on which tools you choose for this, the solution may or may not work across different browser versions.

Figure 11 HTML Math Client

Figure 11** HTML Math Client **

For example, consider the HTML page shown in Figure 11. When you press the Invoke button on this page, it issues an HTTP GET request programmatically using the MSXML2.XMLHTTP class and updates the result textbox with the value found in the returned XML as shown in the following:

function btnInvokeMath_onclick() { var req = new ActiveXObject("MSXML2.XMLHttp.4.0"); var url = "https://localhost/"+ "mathservice/math.asmx/" + selOperation.value + "?x=" + txtX.value + "&y=" + txtY.value; req.open("GET", url, false); req.send(); txtResult.value = req.responseXML.text; }

The code to invoke the Web Service using an HTTP POST is not much different. Just send the URL-encoded data as part of the HTTP body and set the Content-Type header to "application/x-www-form-urlencoded", as shown here:

function btnInvokeMath_onclick() { var req = new ActiveXObject("MSXML2.XMLHttp"); var url = "https://localhost/"+ "mathservice/math.asmx/" + "selOperation.value; var body = "x=" + txtX.value + "&y=" + txtY.value; req.open("POST", url, false); req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); req.send(body); txtResult.value = req.responseXML.text; }

Both of these examples send the requests synchronously, which probably isn't acceptable in most situations, since the browser appears to hang while waiting for the response. You can modify the code to send the request asynchronously by passing in a value of true as the last parameter of the call to open and by supplying a callback function for receiving the response once it arrives.

Overall, these techniques work well as long as you're working with simple interfaces defined completely in terms of simple types. As soon as you start using methods that accept complex types (classes or structs) as parameters to a WebMethod, the ASP.NET infrastructure only supports SOAP invocations. For example, consider the following WebMethod that calculates the distance between two Point instances:

public class Point { public double x; public double y; ••• } [WebMethod] public double CalcDistance(Point orig, Point dest) { return Math.Sqrt( Math.Pow(orig.x-dest.x, 2) + Math.Pow(orig.y-dest.y, 2)); }

In this case, when you browse to the .asmx endpoint, you won't see the test form or the descriptions of the GET/POST invocations because the operation takes complex types that don't naturally map to a URL encoding. Hence, as I just said, you can only invoke this operation using SOAP.

Invoking this operation directly from an HTML page is a bit more tedious because you have to generate the correct SOAP message. Figure 12 shows how to generate the SOAP message along with a Content-Type header of "text/xml" and the appropriate SOAPAction header for this operation. I could have generated the SOAP message programmatically using the DOM API, but it would have required quite a bit more code—it was easier in this case (and sufficiently safe) to just generate it as a string of XML 1.0.

Figure 12 Generating SOAP Messages

function btnInvoke_onclick() { var req = new ActiveXObject("MSXML2.XMLHttp.4.0"); var url = "https://localhost/mathservice/math.asmx"; req.open("POST", url, false); req.setRequestHeader("Content-Type", "text/xml"); req.setRequestHeader("SOAPAction", "https://example.org/math/CalcDistance"); req.send(getSoapRequestMessage(txtOrigX.value, txtOrigY.value, txtDestX.value, txtDestY.value)); txtResult.value = req.responseXML.text; alert(req.responseXML.xml); } function getSoapRequestMessage(origX, origY, destX, destY) { return "<soap:Envelope " + "xmlns:xsi='https://www.w3.org/2001/XMLSchema-instance' " + "xmlns:xsd='https://www.w3.org/2001/XMLSchema' " + "xmlns:soap='https://schemas.xmlsoap.org/soap/envelope/'>" + " <soap:Body>" + " <CalcDistance xmlns='https://example.org/math'>" + " <orig><x>"+origX +"</x>"+<y>"+origY+"</y>"+"</orig>"+ " <dest><x>"+destX +"</x>"+<y>"+destY+"</y>"+"</dest>"+ " </CalcDistance>" + " </soap:Body>" + "</soap:Envelope>"; }

It's clearly more laborious to invoke via SOAP from HTML clients. To help alleviate this, Microsoft provides a DHTML behavior capable of generating the appropriate SOAP message for a given Web Service operation. You can read about the behavior and download the webservice.htc file from ASP.NET. (You should note that DHTML behaviors are only supported in Internet Explorer 5.0 and above.)

To use the Web Service behavior, put webservice.htc into your virtual directory along with your HTML pages and reference it within an HTML element, as shown here:

••• <div id="proxy" style="behavior:url(webservice.htc)"></div> •••

Then you'll have a variable reference to the Web Service behavior object named 'proxy'. Next, you need to initialize the proxy object with the WSDL file for the service (this drives the SOAP message generation process) as shown here:

function window_onload() { proxy.useService( "https://localhost/mathservice/"+ "math.asmx?wsdl", "MathService"); }

And finally, use the generic callService function in order to invoke the operation by name, supplying the necessary input values:

function btnInvokeMath_onclick() { proxy.MathService.callService(resultHandler, selOperation.value, txtX.value, txtY.value); }

The Web Service behavior always invokes the operation asynchronously, so I defined a callback function to harvest the return value once it arrives:

function resultHandler(result) { txtResult.value = result.value; }

If you need to pass in complex types, as is the case with CalcDistance, you can use associative arrays in JavaScript as demonstrated here (the return value is also processed by resultHandler):

function btnInvoke_onclick() { // orig Point instances var orig = new Array(); orig.x = txtOrigX.value; orig.y = txtOrigY.value; // dest Point instance var dest = new Array(); dest.x = txtDestX.value; dest.y = txtDestY.value; // invoke CalcDistance operation proxy.MathService.callService(resultHandler, "CalcDistance", orig, dest); }

You can also process returned objects using the same technique. For example, consider the following WebMethod that returns an array of Point objects:

[WebMethod] public Point[] GetPoints() { return new Point[] { new Point(23, 44), new Point(11, 19), new Point(98, 82) }; }

You can invoke this operation and process the returned Point array as illustrated here (see Figure 13 for the results):

function btnInvoke_onclick() { proxy.MathService.callService(resultHandler,"GetPoints"); } function resultHandler(result) { var points=""; // harvest GetPoints result for (var i=0; i<result.value.length; i++) points += "<h2>Point (x=" + result.value[i].x + ", y=" + result.value[i].y + ")</h2>"; results.innerHTML = points; }

Overall, the Web Service behavior can greatly simplify the process of invoking Web Services via SOAP directly from HTML pages. If a Web Service offers a simpler GET interface, however, you may find it easier to implement across different browser versions.

Figure 13 Invoking GetPoints

Figure 13** Invoking GetPoints **

Around the time both Amazon and Google announced support for their SOAP-based services, many developers began questioning their decision to use SOAP over simpler GET invocations. The ensuing debate has already influenced SOAP 1.2 (see https://www.w3.org/TR/soap12-part2).

Send your questions and comments for Aaron to xmlfiles@microsoft.com.

Aaron Skonnardis an instructor/researcher at DevelopMentor, where he develops the XML and Web Service-related curriculum. Aaron coauthored Essential XML Quick Reference (Addison-Wesley, 2001) and Essential XML (Addison-Wesley, 2000).