Custom Serializers

Any non-trivial web service will require you to handle complex types. There are two basic approachs in pocketSOAP for working with complex types, you can use serializers that map between SOAP complex types and objects within your system, or you can work with the node objects and collections directly.

Sample complex type

The Apache SOAP project includes a sample called the addressbook sample, in this it defines a complex type for a phone number and a complex type for an address, which includes a phone number. Here's an example of an instance of the address type.
<address>
	<number>100</number>
	<street>Spear Street</street>
	<city>San Francisco</city>
	<state>CA</state>
	<zip>94107</zip>
	<phone>
		<area>415</area>
		<exchange>343</exchange>
		<number>3000</number>
	</phone>
</address>

Map the complex type to objects

Rather than using the Node objects as detailed in an earlier section, you want to to serialize and deserialize from SOAP directly into application specific objects, to do this you need to write custom serializer and deserializer objects [or use the property bag serializer].

First off, let define a VB class for the address & phone types, lets keep things simple, and just use public member variables
address.cls
Option Explicit

Public number As String
Public street As String
Public city As String
Public state As String
Public zip As String

Public phone As telephone

Private Sub Class_Initialize()
    Set phone = New telephone
End Sub

phone.cls
Option Explicit

Public area As String
Public exchange As String
Public number As String
Now, lets write some code that create's one of these and sets up the values, and then passes it to pocketSOAP
dim adr 
set adr    = CreateObject("samples.address")
adr.number = "100"
adr.street = "Spear Street"
adr.city   = "San Francisco"
adr.state  = "CA"
adr.zip    = "94107"
adr.phone.area     = "415"
adr.phone.exchange = "343"
adr.phone.number   = "3000"

dim env 
set env = CreateObject("pocketSOAP.Envelope.2")
env.SetMethod "addAddress", "http://address.example.org/"
env.Parameters.Create "address", adr

Serializers

Now, pocketSOAP know's nothing about our address object, or how it should be serialized, so lets write a serializer, this should take an instance of an address object and generate the SOAP/XML representation for it.
addrserializer.cls
Option Explicit

Implements ISoapSerializer

Private Sub ISoapSerializer_Serialize(V As Variant, _
                                        ByVal ctx As PocketSOAP.ISerializerContext, _
                                        ByVal dest As PocketSOAP.ISerializerOutput)
										
    ' pocketSOAP will call us at the point it wants the address object serializing out, 
    ' V will contain the address object instance to serialize
    ' in turn, all we do is ask it to serialize the child items for us
	
    dest.SerializeValue V.number, "number", ""
    dest.SerializeValue V.street, "street", ""
    dest.SerializeValue V.city,   "city",   ""
    dest.SerializeValue V.state,  "state",  ""
    dest.SerializeValue V.zip,    "zip",    ""
    dest.SerializeValue V.phone,  "phone",  ""
End Sub
Now that last line ask pocketSOAP to serialize a phone object, so we'll need to write a serializer for that as well
phoneserializer.cls
Option Explicit

Implements ISoapSerializer

Private Sub ISoapSerializer_Serialize(V As Variant, _
                                        ByVal ctx As PocketSOAP.ISerializerContext, _
                                        ByVal dest As PocketSOAP.ISerializerOutput)
										
    ' pocketSOAP will call us at the point it wants the phone object serializing out, 
    ' V will contain the phone object instance to serialize
    ' in turn, all we do is ask it to serialize the child items for us
	
    dest.SerializeValue V.area, 	"area", 	""
    dest.SerializeValue V.exchange, "exchange", ""
    dest.SerializeValue V.number,   "number",   ""
End Sub
Finally, we need to tell pocketSOAP that it can use these serializers to serializer instances of the address & phone objects.
....
dim env 
set env = CreateObject("pocketSOAP.Envelope.2")
env.SerializerFactory.Serializer "{AC02D0F2-A2A1-4cba-8589-5E4CC2250D81}", "address", "http://address.example/org", "samples.addrserializer"
env.SerializerFactory.Serializer "{870C6692-F3C1-4684-B1AE-47B6ED81604C}", "phone",   "http://address.example/org", "samples.phoneserializer"
env.SetMethod "addAddress", "http://address.example.org/"
env.Parameters.Create "address", adr
Now, pocketSOAP uses Interface ID's to regognise particular objects, if you're not a COM programmer, then that might not make any sense, Once you build the project with the address & phone objects in, you'll be able to find the Interface ID's from the resulting DLL. Run oleview select file->view typelib and open the DLL you've just built. Looking at the library file, you'll see a list of all the properies on the object, something like
    [
      odl,
      uuid(0B451395-7AFF-44E8-BF88-CF35FB1A34D6),
      version(1.0),
      hidden,
      dual,
      nonextensible,
      oleautomation,
      custom(50867B00-BB69-11D0-A8FF-00A0C9110059, 4)    

    ]
    interface _address : IDispatch {
        [id(0x40030000), propget]
        HRESULT number([out, retval] BSTR* number);
        [id(0x40030000), propput]
        HRESULT number([in] BSTR number);
        [id(0x40030001), propget]
        HRESULT street([out, retval] BSTR* street);
        [id(0x40030001), propput]
        HRESULT street([in] BSTR street);
        [id(0x40030002), propget]
        HRESULT city([out, retval] BSTR* city);
        [id(0x40030002), propput]
        HRESULT city([in] BSTR city);
        [id(0x40030003), propget]
        HRESULT state([out, retval] BSTR* state);
        [id(0x40030003), propput]
        HRESULT state([in] BSTR state);
        [id(0x40030004), propget]
        HRESULT zip([out, retval] BSTR* zip);
        [id(0x40030004), propput]
        HRESULT zip([in] BSTR zip);
        [id(0x40030005), propget]
        HRESULT phone([out, retval] _phone** phone);
        [restricted] void Missing18();
        [id(0x40030005), propputref]
        HRESULT phone([in] _phone* phone);
    };
	
The long number in the uuid section is the Interface ID, copy this into your call to Serializer, and repeat for the phone object.

That's it, you're all set, if you add a final line to your application code, wscript.echo e.serialize and run it, pocketSOAP will serialize the envelope, get to the one parameter we added, match the object to the serializer we registered, call the serializer which serializes the address and phone number out to XML.

In many cases, VB uses may prefer to use the propertybag serializer, which doesn't need specific interface ID's to use.

As of pocketSOAP 1.2, the serializerFactory maintains a pool of created serializers, that can be re-used during the [de]serialization process. In this case the serializerFactory will create a single instance of the serializer and make multiple calls to the Serialize function. In the case of the de-serialization process, the deserializer should release all its resources when End is called, once End has been called, the de-serializer is returned to the pool, and it may be asked to de-serialize further items, by start getting called again.

Its highly recommended that if you're going to use either a custom serializer or the propertybag serializer, that you define interfaces to your objects in IDL, and compile them to a typelibrary, and have your VB objects implement the particular interfaces. This gives you much more control over interface versioning, whereas if you leave it to VB, it can and will change the interface ID's on you, breaking the code that maps in your serializers. The full scope on COM for VB programs is way out of scope for pocketSOAP, i'd highly recommend you look at a good COM/VB book such as Ted Patison's excellent Programming Distributed Applications with COM+ and Microsoft Visual Basic 6.0




Copyright

Copyright © Simon Fell, 2000-2004. All rights reserved.