XML serialization of read only properties

1

August 7, 2008 by Alistair Deneys

I was writing a service the other day where I wanted to marshal a custom class across the service boundary. This scenario starts off as a web service (no WCF here…at least not yet anyway). Now it’s quite easy to marshal your own custom class across the web service server to the client…or so you would think. You end up with a class which looks like your class; it has the same properties, but the class is actually completely different. It is just a proxy which was generated from the WSDL your server handed out to your client when you queried it.

This is the work of the wsdl.exe tool which ships with .net (or it is VS?). This tool is used by Visual Studio to create a proxy class for your web service. The problem I was faced with was that I had business logic coded into the entity class which I was trying to return out of the web service call. I also had a few read only properties. I referenced the assembly containing the class in question, but still the wsdl tool created me a proxy. And it’s quite understandable why. If you look at the wsdl (just append ?wsdl to your asmx url) you’ll notice there is no reference to your type’s fully qualified name (such as MyNamespace.MyClass). So it’s no wonder the wsdl tool creates a separate proxy item for you.

The only real way to get your class back into the mix is to hack the generated code which is now included in your solution. Make sure the “Show all Files” option is active in your solution explorer in visual studio, then expand out the “web references” folder, then the service in question, then the Reference.map file under than. There, you’ll find the Reference.cs file which contains the proxy class. If you look at the code in there, you’ll find the proxy method with a return type of your custom class. All you have to do is change this return type and casted return object to use the real referenced class instead of the proxy one. Of course if you ever refresh your web reference you’ll have to change this code again 😦 .

[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://tempuri.org/HelloCustomClass",
  RequestNamespace="http://tempuri.org/",
  ResponseNamespace="http://tempuri.org/",
  Use=System.Web.Services.Description.SoapBindingUse.Literal,
  ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
public CustClass HelloCustomClass()
{
  object[] results = this.Invoke("HelloCustomClass", new object[0]);
  return ((CustClass)(results[0]));
}

So in the above code snippet I would change the CustClass type to use my CustClass defined in my assembly and not the proxy generated class.

If you’re going to be calling the web service asynchronously, you’ll also have to update the return type of the Result property of the <Method>CompletedEventArgs where <Method> is the name of your proxy method.

Now that we’ve got our custom class being used we face the next problem; read only properties. By default web services will use the XML formatter to serialize your object for transport over SOAP. The XML formatter doesn’t process any internal members of your class. It uses the public property getters to take a snapshot of the data and the public setters to restore the data at the other end. The problem is, if you have a read only property the formatter will skip it cause it won’t be able to restore it on the other side. To get around this limitation you can implement the IXmlSerializable interface and implement the WriteXml and ReadXml methods to give you total control over how your object is serialized into XML.

But I only want to transport my object through SOAP so I can use it on the other side. I don’t really care how it goes across the wire and I won’t be interoperating with other technologies (.net only). Do I really have to serialize my object nicely (with property values as XML)?? If all you’re concerned with is getting your object where you need it, welcome the binary formatter. But we’re on the web right? Don’t we have to be some variant of XML? No, but just to keep you happy, welcome the binary formatter and base 64 encoded strings (there…you happy now?). But keep in mind that you don’t actually have to use base 64 encoding, and this is one of the reasons some people prefer to handle serialization themselves.

The binary formatter will process the internal members of your class and produce a byte array. We can then take that byte array and base 64 encode it into a string which will transport over HTTP quite nicely. At the other end it’s just a matter of creating a new object from your deserialized binary data. For the binary formatter to serialize your custom class, the class must be marked as serializable using the Serializable attribute.

Serialization is the easy part:

public void WriteXml(XmlWriter writer)
{
  // Create stream to store data from binary formatter in
  MemoryStream stream = new MemoryStream();

  // Serialize the object onto the stream
  BinaryFormatter bf = new BinaryFormatter();
  bf.Serialize(stream, this);

  // Reset the stream so we can write the data to the XML writer
  stream.Seek(0, SeekOrigin.Begin);

  // Write the data as base 64 encoded
  writer.WriteElementString("CustClass", System.Convert.ToBase64String(stream.GetBuffer()));
}

Did I say the binary formatter output a byte array? Yeah it does, but it wants to push it onto a stream. Now we have the serialized data which constitutes our class instance sitting in an element in the XML called CustClass.

Deserialization is a bit funny. After deserialization we are left with an instance of CustClass inside the ReadXml method of the instance we wish to rehydrate. So we can pull the private members out of the deserialized instance and populate our current instance.

public void ReadXml(XmlReader reader)
{
  if(reader.Read())
  {
    // Get data from XML
    byte[] data = System.Convert.FromBase64String(reader.ReadString());

    // Create stream to store the data in for the binary formatter    
    MemoryStream stream = new MemoryStream();
    stream.Write(data, 0, data.Length);

    // Reset to start of stream so binary formatter can read data
    stream.Seek(0, SeekOrigin.Begin);

    // Deserialize the object data into an object
    BinaryFormatter bf = new BinaryFormatter();
    CustClass stored = (CustClass)bf.Deserialize(stream);

    // Populate internal members
    this.m_name = stored.m_name;
    this.m_privateVal = stored.PrivateVal;
    this.m_value = stored.m_value;
  }
}

Keep in mind that by implementing IXmlSerializable we are supposed to push all our private members into the XML as plain XML. The method described here is quicker if we have a lot of private members which need to be serialized. This method also protects us from class implementation changes which alter the internal members except where the name of the internal members changes and we have to update the ReadXml method.

The IXmlSerializable interface defines 3 methods. We have provided implementation for 2 of them so far. The last remaining method is GetSchema(). This method is called when the wdsl is generated (like above where you appended ?wsdl to the asmx url). If we’re not trying to discover the web service or add a web reference to any other projects, we can get away with not implementing this method. We could also just return null from this method and the WSDL generated will tell clients the custom class element in the SOAP message is “anything”. Otherwise you’ll have to output the schema.

Advertisements

One thought on “XML serialization of read only properties

  1. Aaron Powell says:

    Nice solution to the serialization problem, would you happen to have the source code?

    My only question on it is around the performance of it vs having a ToRealType method. You’re obviously doing extra serialization which would have to take some kind of a performance hit, but is it greater than writing your own cast method?

Leave a Reply

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

Categories

The views expressed on this blog are solely my own and do not necessarily reflect the views of my employer.
%d bloggers like this: