XPO (and other complex types) in .NET web services

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!

5 Comments on XPO (and other complex types) in .NET web services

  1. XPCollection is not support ViewState because it can not Serializable , Some ASP.net UI control (Infragistics’s UltraWebGrid ) must use ViewState;I get this error when try to bind the webgrid(Infragistics’s UltraWebGrid) to an XPCollection .XPCollection companies = new XPCollection (typeof(Company));UltraWebGrid2.DataSource = companies ; UltraWebGrid2.DataBind();”DevExpress.Xpo.XPCollection” must be marked as Serializable or have a TypeConverted different from ReferenceConverter to put in the ViewState.I hope XPO have a class for Convert XPCollection to DataView ,just like ObjectView class in Gentle.NET ;I’m sorry ,my English is pool .

    Like

  2. That’s interesting. Have you contacted DevExpress support about this?

    Like

  3. Thank you for the tip!!

    Like

  4. Hi Oliver, I see I am 3 years late on this post, though my situation is current.

    Your artical is exceptionally pertinent to what we’re building, a layer of business objects on top of a data layer. Each of our custom business objects, eg “Client” inherits from a BusinessObject.

    BusinessObject implements IXmlSerializable, and as per your thread above (“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. :-)”), I am using reflection to dynamically generate the XmlSchema added to the schema set in the static XmlSchemaProvider method which returns an XmlSchemaComplexType, not an XmlQualifiedName. The end result WSDL looks great and is integrated nicely with the Client type, until…

    A problem arises when building the XmlSchema dynamically when creating an XmlSchemaElement with element.XmlSchemaTypeName being the name and namespace of that external type (which is an enum in the same class library.) I get the error:

    Schema type information provided by IXmlSerializable is invalid: Type ‘http://myurl.com/:Client’ is not declared.

    “http://myurl.com/” is the schema TargetNamespace, I suspect that it may have something to do with the face that the XmlSchemaSet is empty when passed to the static method.

    I can send you the code if necessary, difficult to sift through it and remove all my comments etc, especially since I’m not sure if this will find you.

    Please let me know if you can assist and I will furnish you with some code if required.

    Thanks for your help in advance.

    Ashley

    Like

  5. Hi Ashley,

    I’m afraid I haven’t looked into these topics for a long time, so I don’t know how much I can help. But if you want, send me an email and I’ll spare a few minutes to have a look.

    Like

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s