Recently, there have been several requests in the XPO newsgroup about problems with serialization, in conjunction with ASP.NET. I’m still unclear if these issues all had the same sources, but when somebody approached me personally about this today, I thought I’d just look into the web services problem. Please note that I made these tests on the .NET 2 platform, so there may be differences to .NET 1.1. What’s more, ASP.NET and web services are not something I usually have much to do with, so I may certainly be missing something here and there.

Now, what is the problem? Simple: If you have a web method that returns a type that’s derived from one of the XPO base classes (XPObject or XPCustomObject, doesn’t matter), an exception will be thrown when you try to access the service:

InvalidOperationException: DevExpress.Xpo.XPDeletedObject cannot be serialized
because it does not have a parametless constructor.

(Editor: DevExpress, if you’re reading, how about fixing that message? :-))

Similarly, if you try to return an XPCollection, the following exception will be thrown:

NotSupportedException: Cannot serialize interface DevExpress.Xpo.IXPSimpleObject.

So, at a glance one could think that XPO is completely incompatible with web services for these simple reasons. Well… not.

IXmlSerializable

Because web services use XML (SOAP) serialisation to transfer data from one point to another, it’s important that any complex data objects you want to transfer must be convertible by the standard mechanisms. The exception that’s shown for the XPObject return type tells us that there’s a problem with one of the types referenced by one of the base classes - the standard mechanism gives up at that point. To enable conversion of the object’s data to XML, we need to override the defaults and provide our own implementation of the XML creation code. Here’s a simple XPO data class that creates its own XML code for serialisation:

public class DataObject : XPObject, IXmlSerializable {
  public DataObject() { }

  public DataObject(int index, string strVal) {
    this.index = index;
    this.strVal = strVal;
  }

  private int index;
  public int Index {
    get { return index; }
    set { index = value; }
  }

  private string strVal;
  public string StrVal {
    get { return strVal; }
    set { strVal = value; }
  }

  void IXmlSerializable.WriteXml(XmlWriter writer) {
    writer.WriteElementString("Index", XmlConvert.ToString(index));
    writer.WriteElementString("StrVal", strVal);
  }

  void IXmlSerializable.ReadXml(XmlReader reader) { }

  XmlSchema IXmlSerializable.GetSchema() { return null; }
}

Now a method that returns an object of type DataObject will render the following nice XML code when invoked via the web service (and the exception will be gone):

<?xml version="1.0" encoding="utf-8"?>
<DataObject xmlns="http://sturmnet.org/">
  <Index>42</Index>
  <StrVal>One entry</StrVal>
</DataObject>

Type information

The main remaining problem with this is that the WSDL code for the web service will still not contain a type definition for the complex type, so on the consuming side access to the data isn’t as easy as on the service side. To provide the web service with type information that can be used for WSDL generation, the object that implements IXmlSerializable must also deploy the XmlSchemaProvider attribute (this is new in .NET 2.0, I think in 1.1 the GetSchema method of the interface should be used, which is deprecated in 2.0). This attribute defines a static method in the class that must add XML Schema information to the schema set used by the XML serialiser. With that in place, the example class looks like this:

[XmlSchemaProvider("XmlSchema")]
public class DataObject: XPObject, IXmlSerializable {
  ...

  public static XmlQualifiedName XmlSchema(XmlSchemaSet xmlSchemaSet) {
    XmlSerializer schemaSerializer = new XmlSerializer(typeof(XmlSchema));
    StringReader reader = new StringReader(Resources.DataObject);
    XmlSchema schema = (XmlSchema) schemaSerializer.Deserialize(reader);
    xmlSchemaSet.Add(schema);

    return new XmlQualifiedName("DataObject", "http://sturmnet.org/DataObject");
  }
}

The schema itself, which I’m simply loading from a resource in this case, looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<xs:schema id="DataObject"
  targetNamespace="http://sturmnet.org/DataObject"
  elementFormDefault="qualified"
  xmlns="http://sturmnet.org/DataObject"
  xmlns:mstns="http://sturmnet.org/DataObject"
  xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="DataObject" type="DataObject" />

  <xs:complexType name="DataObject">
    <xs:sequence>
      <xs:element name="Index" type="xs:int" />
      <xs:element name="StrVal" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
</xs:schema>

Now the object can pass the important type information on to the WSDL generation process, and on the consumer side a proxy class will be created automatically to deserialise the data from the XML code. Obviously, creating this code for every data class manually would probably be a lot of work. Using the elaborate type information that XPO collects about its data classes, it should be quite easy to implement generic methods that can output reasonable standard XML code and schema definitions for every XPObject. For now, I’m leaving this as an exercise for the reader. :-)

The collection return type

The final issue is that it’s still not possible to have a web method return a complete XPCollection. To work around this, there are two possible solutions:

  1. Create your own collection type, possibly derived from XPCollection, implement IXmlSerializable on that and handle it like a complex type in its own right. This is the most flexible approach, but it’s also a lot of work. Maybe better to
  2. Have the method return a normal array type instead of the complex type. These types work just fine in .NET and they can be handled out of the box by the standard mechanisms.

Using approach (2), a method returning a collection of DataObjects could look like this:

[WebMethod]
public DataObject[] GetData() {
  XPCollection coll = new XPCollection(typeof(DataObject));
  return (DataObject[]) ArrayList.Adapter(coll).ToArray(typeof(DataObject));
}

This works just fine with the XML serialisation support already implemented in the single DataObject, so a list of objects may look like this (in XML, there’ll be a wonderful DataObject[] type in the consumer proxy):

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfDataObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://sturmnet.org/">
  <DataObject>
    <Index>42</Index>
    <StrVal>One entry</StrVal>
  </DataObject>
  <DataObject>
    <Index>52</Index>
    <StrVal>Another entry</StrVal>
  </DataObject>
</ArrayOfDataObject>

So, I hope this will prove helpful - have fun!