From the June 2002 issue of MSDN Magazine.

MSDN Magazine

WS-I, Exposing Stored Procedures as Web Services, and More
Download the code for this article:XML0206.exe (84KB)

Q I've been hearing a lot about WS-I in the press and I still can't figure out how it differs from the W3C. Can you explain its role?

A
WS-I stands for the Web Services Interoperability Organization, a new industry consortium whose goal is to promote interoperability across Web Services implementations. The WS-I was announced in February of this year. Founding members include Microsoft®, IBM, Intel, Oracle, SAP, HP, BEA Systems, Fujitsu, and Accenture. The founding members have already been joined by a long list of additional member companies, all of whom seem serious about ensuring interoperability between their various (and often competing) products and services.
      The need for an organization like WS-I is clear, especially since more and more developers are building Web Services infrastructures and attempting interoperability for the first time. As an example, if you take a handful of today's most prominent SOAP toolkits and attempt to use them with each other out of the box, the results aren't promising. Although the individual Web Services technologies (HTTP, XML, SOAP, and so forth) promote interoperability, bringing them together is far from trivial. It's hard enough to achieve interoperability around a single stable specification; imagine the difficulty when there is a handful of specifications, some of which are still evolving. The devil is in the details.
      A Web Service typically relies on many distinct specifications, some that come from the Internet Engineering Task Force (IETF), others that come from the W3C, and even others that come from new, specialized organizations like UDDI.org. None of these existing organizations are particularly well-suited to tackle the Web Services interoperability problem as it would require them to step outside of their respective domains. It made sense to form a separate consortium for anyone interested in contributing to the overall goal of interoperability. In many ways, WS-I work is complementary to that of existing standards bodies.
      The WS-I process is similar to that of the W3C in many ways. For one thing, it's open; any company can join (although there is an annual membership fee and some agreement papers to sign). The work is organized into activities and executed via working groups, where each company has a say in the result. And finally, compliance is self-audited—you're not going to see WS-I police running around roughing developers up. WS-I will create a suite of conformance testing tools (known as sniffers and analyzers) to assist in clearly evaluating the WS-I conformance level of a given implementation.
      WS-I hopes to lead the technical evolution of Web Services by providing a clear vision of the future and what it will take to get there. It will start by producing clear guidelines for what works today along with general best practices and conventions that promote interoperability. WS-I plans to organize its work into profiles—groups of related specifications that can be used to build a certain class of applications.
      Currently WS-I is focusing on the Basic Profile, which consists of the core specifications needed to make simple Web Services work including XML, XML Schema, SOAP, Web Services Description Language, and Universal Description, Discovery, and Integration (see Figure 1).

Figure 1 Specs
Figure 1 Specs

      Once widespread interoperability has actually been achieved, additional profiles will tackle higher-level application capabilities not supported by Web Services today, such as security, reliability, transactions, non-XML message formats, and the list goes on. WS-I is a huge win for the industry. For more information and details on how to join, point your browser to https://www.ws-i.org.

Q
Is it possible to directly expose a stored procedure as a Web Service in SQL Server™ 2000?

A
Although it's not supported in SQL Server 2000 out of the box, you can add the support through the recently released SQLXML 3.0 components. Just download SQLXML 3.0 from https://www.microsoft.com/sql and install the bits. Then you'll have a new IIS Virtual Directory Management console specifically designed for SQLXML 3.0. The management console looks a lot like the one built into SQL Server 2000 for configuring XML-based vroots, but it has more features, including the one you're after.
      Let me walk you through the process of exposing a typical stored procedure, like the one shown here, as a Web Service:

  CREATE PROCEDURE AddUser
  
@name varchar(255),
@email varchar(255),
@owfpw varchar(255)
AS
INSERT INTO Users VALUES (@email, @name, @owfpw)

 

This sproc simply adds a new user to the system. To configure this sproc as a Web Service, you first need to configure an XML-enabled virtual root (as before with SQL Server 2000). Then you need to add a new virtual name of type "soap." The path you specify here is the path where the generated WSDL file that describes the Web Service will go. Also, the name you use for the Web Service is the name that will show up in the WSDL document (see Figure 2).

Figure 2 Adding a Virtual Name for SOAP
Figure 2 Adding a Virtual Name for SOAP

      Once you've created the virtual name, you can configure it in the configuration dialog, which allows you to add new operations (methods) to your Web Service by selecting from the existing sprocs and templates your system knows about (see Figure 3). Now when you save the new settings, you should be able to find a WSDL document in the path you specified earlier.

Figure 3 Configuring a SOAP Virtual Name
Figure 3 Configuring a SOAP Virtual Name

      Now that you have a WSDL document that accurately describes the sproc-based Web Service, anyone can call it via XML messaging. The WSDL document for this particular sproc specifies that the request message must look like this:

  <soap:Envelope 
  
xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<AddUser
xmlns="https://SKONNARDT20/bugsdb/UserManagement">
<name>Aaron</name>
<email>aaron@skonnard.com</email>
<owfpw>xoxoxo</owfpw>
</AddUser>
</soap:Body>
</soap:Envelope>

 

You could save this into a text file and post it to the vroot you just configured to invoke the sproc. But if you'd rather work with a Web Service toolkit (like Visual Studio® .NET), you can generate some classes that automate the building/posting of XML messages to the vroot. For example, simply run:

  wsdl.exe UserManagement.wsdl
  

 

Now you'll have a proxy class that knows how to automatically generate and receive SOAP messages against the vroot. The following client application illustrates how to use the generated proxy to invoke the sproc:

  Using System;
  
public class AddUser
{
public static void Main(string[] args)
{
if (args.Length != 3)
{
Console.WriteLine("usage: AddUser name
email pwd");
return;
}
UserManagement um = new UserManagement();
um.AddUser(args[0], args[1], args[2]);
}
}

 

The SOAP message shown here is what's actually going across the wire when AddUser is called.

Q
I would like to use an XPointer in the Microsoft Internet Explorer address bar to load only part of my XML. Is it possible to do that?

A
Neither MSXML nor .NET supports XPointer today. Although XPointer has been close to W3C Recommendation status for a while now, last year it ran into some serious legal snags related to licenses and intellectual property concerns. Since then, it seems to have taken more steps backwards. There have even been recent posts to XML-DEV (https://www.xml.org) suggesting the specification be carved down to the bare bones to get it out. With all this uncertainty, you can't blame vendors for holding off on support.
      You can subset documents today using XPath, as seen here:

  XmlDocument doc = new XmlDocument();
  
doc.Load("authors.xml");
XmlNodeList nl = doc.SelectNodes("/publisher/title/author");
// process nl here

 

But this requires you to load the entire document into memory before applying the XPath expression.
      If what you want is to subset the original XML 1.0 document before loading it into the DOM, you can achieve it by either writing a custom SAX ContentHandler (MSXML) or by extending XmlReader (.NET). Either way, your custom loader would have to implement the logic to skip over the nodes you don't care about while walking through the stream. The DOM loading code would then utilize the custom XmlReader:

  // you write MyXPathReader or something 
  
// similar
XmlReader r = new MyXPathReader("authors.xml");
r.Filter = "/publisher/title/author";
XmlDocument doc = new XmlDocument();
doc.Load(r); // only the subset of authors is loaded

 

      And if you really want Internet Explorer integration (being able to type the expression into the address bar), you could even write a Browser Helper Object (BHO) that uses your component.

Q
Is there a way to tell Internet Explorer that when it loads a certain XML file, it should collapse all nodes?

A
Internet Explorer uses an internal XSLT document to transform every XML 1.0 document that you click on into the DHTML view you're seeing in the browser. The transformation file is actually tucked away in msxml.dll as an HTML resource called DEFAULTSS.XSL. If you open msxml3.dll into msdev.exe as a resource file, you'll find it there (see Figure 4).

Figure 4 XSLT Document
Figure 4 XSLT Document

      You can also use the Internet Explorer res: protocol to display it. Try the following URL in Internet Explorer:

  res://msxml3.dll/defaultss.xsl
  

 

You should see the built-in transformation file (see Figure 5).

Figure 5 Using DefaultSS.XSL in Internet Explorer
Figure 5 Using DefaultSS.XSL in Internet Explorer

      Unfortunately there's no way to configure or parameterize the transformation. Each time Internet Explorer loads an XML file, however, it looks for a special processing instruction called xml-stylesheet. If it finds one, it locates the specified XSLT transformation and uses it instead of DEFAULTSS.XSL. The following XML document fragment illustrates how it's used:

  <?xml-stylesheet href="collapsed.xsl" type="text/xsl"?>
  
<people>
<person>
<name>Scott</name>
<age>30</age>
</person>
•••

 

      If you like the display created by DEFAULTSS.XSL, but would rather everything start out collapsed, you can simply make a few minor adjustments to DEFAULTSS.XSL and reference it as I've just shown. I've provided a sample called collapsed.xsl which illustrates how this works. Now when I load people.xml (which uses xml-stylesheet) it should look like Figure 6.

Figure 6 Using Collapsed.XSL Upon Loading
Figure 6 Using Collapsed.XSL Upon Loading

And after clicking on the root element, it expands to look like Figure 7.

Figure 7 Using Collapsed.XSL After Clicking Root
Figure 7 Using Collapsed.XSL After Clicking Root

Q
Consider the following XML schema fragment which takes advantage of substitution groups:

   •••
  
<!-- define substitution group -->
<xsd:element name="per" type="this:Person" />
<xsd:element name="emp" type="this:Employee"
substitutionGroup="this:per"/>

<xsd:complexType name="People">
<xsd:sequence minOccurs="0"
maxOccurs="unbounded">
<xsd:element ref="this:per" />
</xsd:sequence>
</xsd:complexType>
<xsd:element name="people" type="this:People"/>
•••

 

Then consider the following instance document which takes advantage of substitution and therefore contains a mixture of per and emp elements:

     <z:people xmlns:z="urn:zoo">
  
<z:per>
<name>Tiff</name>
</z:per>
<z:emp>
<name>Bob</name>
<id>333-23-5525</id>
<salary>2343.95</salary>
</z:emp>
</z:people>

 

      Using Microsoft APIs, is it possible to figure out the real type of an element while programming against this document, or is there a way to locate the substitution group of a given element?

A
Both are actually possible in either MSXML 4.0 or the .NET framework. The following code shows how to use the .NET XmlValidatingReader to process an XML stream and look up such type information:

  void ValidateAndPrintTypes()
  
{
XmlValidatingReader r = new XmlValidatingReader(
new XmlTextReader("zoo.xml"));
r.Schemas.Add("urn:zoo", "zoo.xsd");
while (r.Read())
{
if (r.SchemaType != null)
{
object obj = r.SchemaType;
XmlSchemaComplexType ct = obj as XmlSchemaComplexType;
if (ct != null) Console.WriteLine(ct.Name);
}
}
r.Close();
}

 

      Because the substitution information is part of the schema, the processor can automatically resolve the actual type of the substituted emp element and, as a result, perform the correct validation in that case. This code produces the following console output:

  People
  
Person
Employee

 

      You can also walk through the Schema Object Model (SOM) and look up substitution information for a given element. For example, the following function takes an XmlSchema object and prints out the substitution group of each global element:

  static void FindSubstitutionGroups(XmlSchema s)
  
{
foreach (XmlSchemaElement e in s.Elements.Values)
Console.WriteLine("element: {0}, substitution: {1}",
e.Name, e.SubstitutionGroup.Name);
}

 

      You can extend the previous ValidateAndPrintTypes function to call FindSubstitutionGroups with the following line:

  FindSubstitutionGroups(r.Schemas["urn:zoo"]);
  

 

      This complete .NET sample is available for download from the link at the top of this article. The XML Schema support in MSXML 4.0 is very similar. If you're working in that space, you'll want to check out the IXMLDOMSchemaCollection[2] and ISchema derived interfaces and pay close attention to the getDeclaration method.

Q
I want a collection of all the different attributes within the ITEMS element—not the attribute values, but the attribute names themselves. For example, consider the following document:

  <ITEMS>
  
<ITEM PI="p2" TI="t2" L="L1" />
<ITEM PI="2312" TI="77" L="1241" />
<ITEM PI="2312" TI="2272" L="336" DATA="1" />
</ITEMS>

 

In this case, the XPath expression should identify the {PI, TI, L, DATA} attributes.

A
It's likely that you tried the following expression as you were experimenting:

  //@*[not(name() = name(preceding::*/@*)]
  

 

This doesn't work because of the way name behaves. The name function will only give you the name of the first node in the identified node-set (it doesn't test each one individually as in a node-set comparison). You may have then tried to get around this by using a nested predicate as shown here:

  //@*[not(preceding::*/@*[name() != ???])]
  

 

But now you can't get back to your original context to supply the correct string where ??? is shown.
      You can solve this problem using XSLT 1.0 keys:

  <xsl:transform version="1.0"    
  
xmlns:xsl="https://www.w3.org/1999/XSL/Transform" >
<xsl:output method="text"/>
<xsl:key name="atts" match="@*" use="name()" />
<xsl:template match="/">
unique attributes:
<xsl:for-each select="//@*[generate-id(.) =
generate-id(key('atts', name())[1])]">
*** <xsl:value-of select="name()" /> ***
</xsl:for-each>
</xsl:template>
</xsl:transform>

 

In addition to this approach, other XSLT processors may provide extension mechanisms for performing this particular task.

Q
I vaguely remember seeing a way to do HTML screen scraping in .NET. Can you point me in the right direction?

A
If you search for "Creating XML Web Services that Parse the Contents of a Web Page" in the Visual Studio .NET documentation, you'll find an example. The basic idea is to describe the Web Service using WSDL. You specify HTTP GET or POST as the service binding, then map the input message to a URL-encoded string and the output message to HTML. Then, within the output message binding, you can specify regular expressions that will be used to mine the desired data from the returned HTML page:

  •••
  
<binding name="GetTitleHttpGet" type="s0:GetTitleHttpGet">
<http:binding verb="GET"/>
<operation name="TestHeaders">
<http:operation location="MatchServer.html"/>
<input>
<http:urlEncoded/>
</input>
<output>
<text
xmlns="https://microsoft.com/wsdl/mime/textMatching/">
<match name='Title' pattern='TITLE&gt;(.*?)&lt;'/>
<match name='H1' pattern='H1&gt;(.*?)&lt;'/>
</text>
</output>
</operation>
•••

 

      When the proxy is generated using wsdl.exe, there will be a class that holds the matched information.

Q
With MSXML 4.0, if I select a node from a document and call transform, the transformation starts with the selected node as the context node. Try as I might, I cannot duplicate this behavior with the .NET XML classes. The context node is always taken as the document root node.

A
You're right. XslTransform always starts processing at the root of the input tree even if you've moved the underlying XPathNavigator cursor. Apparently, it was designed this way for this release. However, you can still use transformations that are specific to given nodes in a larger XML document. Take the following XSLT document, for example:

  <xsl:transform version="1.0" 
  
xmlns:xsl="https://www.w3.org/1999/XSL/Transform">
<xsl:template match="person">
Name: <xsl:value-of select="."/>
</xsl:template>
</xsl:transform>

 

      Let's assume that you used to execute this transformation while positioned on a person element in the DOM via MSXML. You can accomplish the same result in .NET by simply copying the context node into a temporary XmlDocument buffer that you can run through the transformation engine:

  XslTransform tx = new XslTransform();
  
tx.Load("person.xslt");
XmlDocument doc = new XmlDocument();
doc.Load("person.xml");
XmlDocument context = new XmlDocument();
// copy context node into separate tree
context.LoadXml(doc.SelectSingleNode(
"/people/person[2]").InnerXml);

 

      Then you simply pass the copied document to Transform so the context node is the only one in the XSLT input tree:

  tx.Transform(context, null, Console.Out);
  

 

      Because XSLT has a built-in template for match="/" that calls apply-templates, you don't have to worry about defining that one and it should work as before.

Q
Somewhere in the MSXML documentation, it specifies that the type mapping for XSLT extension functions is as follows: numbers are coerced to double, everything is coerced into a string, and objects return an error. This seems to agree with Figure 10 in your March 2002 column. But in all my tests, this doesn't seem to be the case. It seems that the complete type mapping, shown in Figure 7 of the same column, applies everywhere. What's the deal?

A
Although the XSLT type system mapping documentation for .NET is quite clear, it's a bit muddy for MSXML. I ran across the same blurb in the documentation while writing that piece, but unfortunately it happens to be wrong. The funny thing is that all of my previous experiments confirmed it to be correct.
      The reason I wasn't able to return node-sets or result tree fragments from extension functions was that I was using a different DOM version in the extension function from what I was using to load and execute the transformation documents. I was using my transform.js command-line utility, which is written against MSXML 3.0, while my extension functions were trying to return MSXML 4.0 objects. If the DOM versions don't match, it will produce an error. Figure 10 from that piece, along with the supporting paragraphs, may be garbage-collected from your memory at will. The mapping shown here in Figure 8 is actually supported in both directions. Sorry for the confusion.

Send questions and comments for Aaron to xml@microsoft.com.

Aaron Skonnard is 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, 2001). Get in touch with Aaron at https://staff.develop.com/Aarons.