24 October 2005

Lesson learnt: Implement IXmlSerializable to override Wsdl definition (Part I)

This is part one of implementing IXmlSerializable to customised object serialization.

Overriding WSDl definition is easy. You just need to implement IXmlSerializable for your type definition. IXmlSerializable contains three methods that is used by wsdl serialisation.
GetSechma: Implement this method to supply our custom schema definition.
WriteXml: Implement this method to write xml fragment
ReadXml: Tells your object how to de-serialise xml doc into an object of this type.

Here is a sample:


public class Foo: IXmlSerializable
{
private string _memberVar;

/// default constructor used by XmlSerializer
public Foo(){ }


XmlSchema IXmlSerializable.GetSchema()
{
Stream s = null;
try
{
s = base.GetSchema(typeof (Foo), "Foo.xsd");
s.Position = 0;
s.Flush();
XmlSchema schema = XmlSchema.Read(s, null);
schema.Compile(null);
return schema;
}finally
{
s.Close();
}
}

// ... if use default behaviour for serialization
// [XmlElement("MemberVar")]
// public String MemberVar
// {
// get {return _memberVar;}
// set { //serializer requires this }
// }

/// we do not implement this method because we only intend to use it for serialisation (from object to xml data)
void IXmlSerializable.ReadXml(XmlReader reader)
{
throw new NotImplementedException();
}

void IXmlSerializable.WriteXml(XmlWriter writer)
{
//
// dwiohfjwhfjekfhjsdkhf
//


// Do somthing to load _membervar?
writer.WriteElementString("MemberVa", _memberVar);

//WARNING: Don't close the writer here! Let serilizer do it for you!!!
//writer.Close();
}
}
}


Foo.xsd is supplied as an embedded resource:

<xs:schema id="Foo" targetNamespace="http://mynamespace.jingye.com" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="Foo">
<xs:complexType>
<xs:sequence>
<xs:element name="MemberVar" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

This would serialize a Foo object to:

<Foo>MemberVar>Some Data</MemberVar>l</Foo>

And the parent element (Say ‘Bar’) of Type Foo could have WriteXml like this

public class Bar : IXmlSerializable
{
public Foo[] Item;
...
public void WriteXml(XmlWriter writer)
{
foreach (Foo r in Item)
{
writer.WriteStartElement("Foo");
((IXmlSerializable)r).WriteXml(writer);
writer.WriteEndElement();
}
}

This would serialize a Bar object to:

<Bar>
<Foo><MemberVar>Some Data 1</MemberVar></Foo>
<Foo><MemberVar>Some Data 2</MemberVar></Foo>
</Bar>


So when does serialisation happen, when WriteXml and ReadXml gets called?

WriteXml is called when XmlSerializer is to marshal an object, you can verify this by knock off a quick (nunit) test case like this:

[Test]public void Test()
{

XmlDocument expectedXml = new XmlDocument();
//Build your expected result
///...

Foo aFoo = new Foo(...); //if your constructor takes param to initialize memberVar

XmlSerializer xs = new XmlSerializer(typeof (Foo));

xtwriter.Formatting = Formatting.None;


// WriteXml() is called here
xs.Serialize(xtwriter, aFoo);

XmlDocument doc = new XmlDocument();
xtwriter.Flush();
ms.Position = 0;
ms.Flush();
doc.Load(ms);

Assert.AreEqual(expectedXml.OuterXml, doc.OuterXml);
}

GetSchema is called when wsdl is invoked to generate the type definition. This could be using http://MyNamespace.Jingye.com/SomeWebService.asmx?wsdl or by using MS wsdl tool at command line to generate client proxy class.

However, if you do either of that, and if you look carefully, you will see a problem. I will talk about it in the second part.

No comments: