Microsoft SOAP Toolkit Type Mappers

 

Mike Deem
Microsoft Corporation

June 5, 2002

Introduction

The most useful functionality offered by SOAP implementations is the ability to convert data between a platform-specific format and the XML format used in SOAP messages. It is this functionality that allows an application running on one platform to convey information to an application running on another platform.

Exactly how such conversions are done varies from implementation to implementation. The Microsoft® SOAP Toolkit uses type mappers to convert values contained in the COM-defined VARIANT structure. The SOAP Toolkit provides a number of built-in type mappers that handle standard VARIANT types, such as VT_BSTR, VT_DATE, and so on. You can create your own custom type mappers to handle other data types, such as your own objects.

Note The examples in this document are for version 3.0 of the Microsoft SOAP Toolkit. You'll find a link to the SOAP Toolkit download on MSDN's SOAP Web page.

XML as Used in SOAP

To really understand type mappers, you must understand something of the way XML is used in SOAP messages. The XML in a SOAP message, like all XML documents, contains nothing but text. However, in a SOAP message, this text represents data of specific types. SOAP allows two fundamental methods for expressing typed data in XML: literal XML and SOAP encoding.

With both literal XML and SOAP encoding, the text format used to represent simple typed data, such as a date or a floating point number, is defined in XML Schema Part 2: Datatypes. For example, a value with thedateTimetype would be represented with a string something like 1999-05-31T13:20:00-05:00. Note that this format does not match the format that you would typically get from a Win32 API call or the Microsoft® Visual Basic® FormatDateTime function. By following the rules defined for XML schema data types, a dateTime value sent by one SOAP implementation can be understood by another.

Also, for both literal XML and SOAP encoding, XML schema structures and data types in a WSDL document are used to describe the type of data in a SOAP message. However, exactly what the XML schema means is different for literal XML and SOAP encoding.

The WSDL document also specifies whether SOAP encoding or literal XML are used for a particular SOAP message.

Literal XML

When the literal XML representation is used for a SOAP message, the XML in the message will be exactly as described by the XML schema for the message in the WSDL document. The use of literal XML simplifies the job of a type mapper, resulting in better interoperability between implementations.

SOAP Encoding

When the SOAP encoding representation is used for a SOAP message, the XML in the message will follow the rules defined in section 5 of the SOAP specification. Conventions have been adopted by many SOAP implementations that allow the content of the message to be described using XML schema in the WSDL document. Such descriptions are not a literal description of the message, but are interpreted by an implementation to produce a SOAP encoding format message. This imprecise message definition format can make interoperability between implementations difficult to achieve.

The purpose of SOAP encoding is to define an XML representation for common programming language constructs such as structures and arrays. To accomplish this, SOAP encoding introduces attributes such as href and arrayType that may appear in a SOAP message. The href attribute allows relationships between elements to be expressed (much like a pointer in many programming languages). The arrayType attribute describes how a sequence of elements gets mapped into a single- or multi-dimensional array. A type mapper must properly handle and generate such attributes when converting values between XML and COM.

Built-In Type Mappers

The SOAP Toolkit comes with a number of type mappers that handle the conversion between many standard XML data types and COM data types. The table below lists the XML schema types and the COM data type to which values are converted. XML data types that are not listed are all converted to VT_BSTR. A type mapper for arrays of these types is also provided.

Table 1. Built-in type mappers

XML Schema Type COM Type
base64Binary VT_ARRAY | VT_UI1
boolean VT_BOOL
byte VT_I2
date VT_DATE
dateTime VT_DATE
decimal VT_DECIMAL
double VT_R8
float VT_R4
int VT_I4
integer VT_DECIMAL
long VT_DECIMAL
negativeInteger VT_DECIMAL
nonNegativeInteger VT_DECIMAL
nonPositiveInteger VT_DECIMAL
positiveInteger VT_DECIMAL
short VT_I2
string VT_BSTR
time VT_DATE
unsignedByte VT_UI1
unsignedInt VT_DECIMAL
unsignedLong VT_DECIMAL
unsignedShort VT_UI4

Using Built-In Type Mappers with the Low-Level API

You can use the SOAP Toolkit provided type mapper with low-level API to generate and parse the text in a SOAP message. You use a SoapTypeMapperFactory30 object to get the type mapper object for the XML data type you wish to convert. For example, the following code uses thedateTimemapper:

Dim Connector As New HttpConnector30
Dim Reader As New SoapReader30
Dim Serializer As New SoapSerializer30
Dim Factory As New SoapTypeMapperFactory30
Dim Mapper As ISoapTypeMapper
Dim D As Date

Set Mapper = Factory.GetMapper(enXSDdatetime, Nothing)

Connector.Property("EndPointURL") = _
    "https://localhost:8080/SoapToolkitTypeMappers/Echo.WSDL"
Connector.Property("SoapAction") = _
    "http://tempuri.org/Echo/action/Echo.EchoDate"

Serializer.Init Connector.InputStream

Serializer.StartEnvelope
Serializer.StartBody
Serializer.StartElement _
    "EchoDate", _
    "http://tempuri.org/Echo/message/", _
    "NONE"
Serializer.StartElement "D"
Mapper.Write _
    Serializer, _
    "", _
    enDocumentLiteral, _
    0, _
    Now()
Serializer.EndElement
Serializer.EndElement
Serializer.EndBody
Serializer.EndEnvelope

Reader.Load Connector.OutputStream

D = Mapper.Read( _
        Reader, _
        Reader.RpcResult(), _
        "", _
        enDocumentLiteral, _
        0)

This code will produce a SOAP message similar to the following:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<SOAP-ENV:Envelope
 xmlns:SOAPSDK1="http://www.w3.org/2001/XMLSchema"
 xmlns:SOAPSDK2="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:SOAP-ENV="https://schemas.xmlsoap.org/soap/envelope/">
 <SOAP-ENV:Body>
  <SOAPSDK4:EchoDate
   xmlns:SOAPSDK4="http://tempuri.org/Echo/message/">
   <D>2002-05-29T06:03:21Z</D>
  </SOAPSDK4:EchoDate>
 </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Note that the code above specifies that literal XML be used. To use SOAP encoding, make the following changes:

...

Serializer.StartElement _
    "EchoDate", _
    "http://tempuri.org/Echo/message/", _
    "https://schemas.xmlsoap.org/soap/encoding/"

...

Mapper.Write _
    Serializer, _
    "https://schemas.xmlsoap.org/soap/encoding/", _
    enRPCEncoded, _
    0, _
    Now()

...

D = Mapper.Read( _
        Reader, _
        Reader.RpcResult(), _
        "https://schemas.xmlsoap.org/soap/encoding/", _
        enRPCEncoded, _
        0)

...

The SOAP message will now be similar to the following:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<SOAP-ENV:Envelope
 xmlns:SOAPSDK1="http://www.w3.org/2001/XMLSchema"
 xmlns:SOAPSDK2="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:SOAPSDK3="https://schemas.xmlsoap.org/soap/encoding/"
 xmlns:SOAP-ENV="https://schemas.xmlsoap.org/soap/envelope/">
 <SOAP-ENV:Body>
  <SOAPSDK4:EchoDate
   xmlns:SOAPSDK4="http://tempuri.org/Echo/message/"
   SOAP-ENV:encodingStyle="https://schemas.xmlsoap.org/soap/encoding/">
   <D>2002-05-29T06:03:21Z</D>
  </SOAPSDK4:EchoDate>
 </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

The Generic Custom Type Mapper

The type mappers provided with the SOAP Toolkit can handle the most common "simple" XML data types and arrays for those types. A simple data type is a type without any internal structure, such as child elements or attributes. You can write your own custom type mappers to handle complex data types, such as your own COM objects. You can also write a custom type mapper to replace a built-in mapper for a simple data type.

Version 3.0 of the SOAP Toolkit also provides a generic custom type mapper. The generic mapper can handle COM objects that expose all their data as public read/write properties without indexes. The most common scenario that requires the use of your own custom type mapper, instead of the generic custom type mapper, is a COM collection-style class.

Using the Generic Custom Type Mapper in a Service with the High-Level API

To use the generic custom type mapper in a service, you simply pass a COM object as a parameter to a service method and generate a WSDL and WSML document using the WSDL Generator. For example, create a COM object, Name, with public First and Last properties:

Public First As String
Public Last As String

Also create an EchoName function in a service object:

Public Function EchoName(N As Name) As Name
    Set EchoName = N
End Function

The WSDL generated for this function will contain the following excerpts:

...

<schema
   targetNamespace='http://tempuri.org/Echo/type/'
   xmlns='http://www.w3.org/2001/XMLSchema'
   xmlns:SOAP-ENC='https://schemas.xmlsoap.org/soap/encoding/'
   xmlns:wsdl='https://schemas.xmlsoap.org/wsdl/'
   elementFormDefault='qualified'>

   ...

   <complexType  name ='_Name'>
      <sequence>
         <element name='First' type='string'/>
         <element name='Last' type='string'/>
      </sequence>
   </complexType>

   ...

</schema>

...

<message name='Echo.EchoName'>
   <part name='N' type='typens:_Name'/>
</message>

<message name='Echo.EchoNameResponse'>
   <part name='Result' type='typens:_Name'/>
</message>

...

Note that the type name in the WSDL is _Name instead of Name. This is because the WSDL Generator creates type definitions based on interface names, not class names. Visual basic always creates an interface name by prefixing the class name with an underscore character. You can safely remove the underscore in the WSDL and WSML documents if you wish.

The WSML document generated will contain:

    <using 
      PROGID='MSSOAP.GenericCustomTypeMapper30' 
      cachable='0' 
      ID='GCTM' />
    <types>
      <type 
        name='_Name' 
        targetNamespace='http://tempuri.org/Echo/type/' 
        uses='GCTM' 
        targetPROGID='TypeMapperService.Name' 
        iid='{3BE1F5FA-DB87-4CB5-AD16-3574388ADBF0}'/>
    </types>

The<using>tag associates the generic custom type-mapper object with the ID "GCTM". The<type>indicates that the generic custom type mapper is to be used for the type _Name as defined in the schema with a targetNamespace attribute that has the value "http://tempuri.org/Echo/Type/".

When initialized, the generic custom type mapper will look in the schema to determine the child elements defined by the _Name type. Using a SoapTypeMapperFactory30 object, it then creates a type mapper for each element. When asked to read a _Name from a SOAP message, the mapper will create an object with the programmer ID specified by the targetPROGID attribute and read its First and Last property values from the SOAP message using the type mapper previously created for each field. When asked to write a _Name to a SOAP message, the mapper will write its First and Last property values using the type mapper previously created for each field.

Using the Generic Custom Type Mapper in a Client with the High-Level API

Using the generic custom type mapper in a client first involves creating COM objects for each of the service's custom types. If you are writing both the client and service using the SOAP Toolkit, you may be able to reuse the service class in the client. If not, you'll have to look in the WSDL for the service, identify the complex types used as parameters, and create a COM object for each.

The COM object you create should have one public property for each of the custom type's child elements. If the child element has a standard XML simple type, the property in the COM object would have the corresponding type as shown in the table above. If the element has a complex type, you would define a COM object for that complex type.

You can then construct a WSML document for use on the client that identifies the generic custom type mapper and specifies that it should be used for each custom type.

For example, suppose the WSDL contained the following excerpts:

...

<schema
   targetNamespace='http://example.com/'
   xmlns:tns='http://example.com/'
   xmlns='http://www.w3.org/2001/XMLSchema'
   elementFormDefault='qualified'>

   ...

   <complexType name='Foo'>
      <sequence>
         <element name='a' type='tns:Bar'/>
         <element name='b' type='string'/>
      </sequence>
   </complexType>

   <complexType name='Bar'>
     <sequence>
       <element name='a' type='string'/>
       <element name='b' type='string'/>
     </sequence>
   </complexType>

   ...

</schema>

...

<message name='SubmitFoo'>
   <part name='F' type='tns:Foo'/>
</message>

...

You would create a COM object named Foo and a COM object named Bar. The Foo object would contain the following fields:

Public a As Bar
Public b As String

The Bar object would contain:

Public a As String
Public b As String

The WSML would specify:

    <using 
      PROGID='MSSOAP.GenericCustomTypeMapper30' 
      cachable='0' 
      ID='GCTM' />
    <types>
      <type 
        name='Foo' 
        targetNamespace='http://example.com/' 
        uses='GCTM' 
        targetPROGID='MyProject.Foo' 
        iid='{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'/>
      <type 
        name='Bar'
        targetNamespace='http://example.com/' 
        uses='GCTM' 
        targetPROGID='MyProject.Bar
        iid='{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'/>
    </types>

You can find the interface ID for the interfaces for the Foo and Bar objects using a tool such as the OLE/COM Object Viewer that comes with Microsoft® Visual Studio® 6.0 and the Platform SDK. Select the File, View TypeLib menu item, and locate the DLL that contains the Foo and Bar classes. Visual Basic will have created _Foo and _Bar interfaces.

Using the Generic Custom Type Mapper with the Low-Level API

You can use the generic custom type mapper with the low-level API. Doing so requires that you provide an XML schema that defines the complex types and the <type> tags as found in the WSML document. You can then create a SoapTypeMapperFactory30 object and register the generic custom type mapper as the mapper for the custom types.

For example, assume you had the schema (not the entire WSDL document) that defined the Foo and Bar types above in a file called "Example.xsd" and had the WSML above in a file called "Example.wsml". You could then write code similar to the following:

    Dim Factory As New SoapTypeMapperFactor30
    Dim Serializer As New SoapSerializer30
    Dim SchemaDoc As New DOMDocument40
    Dim WsmlDoc As New DOMDocument40
    Dim WsmlNode As IXMLDomNode
    Dim FooMapper As ISoapTypeMapper
    Dim Foo As New Foo

    SchemaDoc.Load "Example.xsd"
    Factory.AddSchema SchemaDoc.documentElement
    
    WsmlDoc.Load "Example.wsml"
    For Each WsmlNode In WsmlDoc.selectNodes("//type")
        Factory.AddCustomMapper _
            "MSSOAP.GenericCustomTypeMapper30", _
            WsmlNode
    Next
    
    Set FooMapper = _
        Factory.GetTypeMapperByName("Foo", "http://example.com")
    
    Set Foo.a = New Bar
    Foo.a.a = "Bar's a"
    Foo.a.b = "Bar's b"
    Foo.b = "Foo's b"
    
...

    Mapper.Write _
        Serializer, _
        "https://schemas.xmlsoap.org/soap/encoding/", _
        enRPCEncoded, _
        0, _
        Foo
  
... 

Conclusion

Type mappers are a fundamental part of the Microsoft SOAP Toolkit. You can use the built-in type mappers or the generic custom type mapper to convert between many COM data types and XML. However, you may also wish to create your own custom type mappers. See the SOAP Toolkit documentation and sample applications for more information.

 

At Your Service

Mike Deem is a program manager on the Microsoft SOAP Toolkit team. He is also part of Microsoft's XML technologies team, where he focuses on using XML and Web services for data access.