Using the XmlSerializer as an XmlObjectSerializer with WCF

Published 18 January 08 08:06 PM | andarno 

The Windows Communication Foundation introduced the new DataContractSerializer to replace the XmlSerializer in many scenarios.  The DataContractSerializer is faster than the XmlSerializer, but has certain limitations.  For example it does not support serializing an object's members as XML attributes.  When you write a WCF client that calls a service, WCF will generate a proxy class that defaults to using the DataContractSerializer and falls back to the XmlSerializer if the service uses features that the DataContractSerializer does not support.  This fallback mechanism is automatic if you're using WCF's service model.

But if you're working at WCF's messaging layer, you must explicitly choose which serializer to use.  You can use any serializer that derives from System.Runtime.Serialization.XmlObjectSerializerSystem.Runtime.Serialization.DataContractSerializer already derives from this class -- but System.Xml.Serialization.XmlSerializer does not.  So you have some work to do before you can use the XmlSerializer as your serializer of choice at the messaging layer of WCF.

In order to use the XmlSerializer to send messages with WCF, we have to define a class that derives from XmlObjectSerializer and uses the XmlSerializer internally.  In essence we will create a wrapper class for the XmlSerializer that makes it look and work like an XmlObjectSerializer.  XmlObjectSerializer is an abstract class with only a few methods we need to implement. 

Below is a sample of a class that will do just that.

namespace Microsoft.ServiceModel.Samples
{
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Text;
    using System.Xml;
    using System.Xml.Serialization;
    using System.Runtime.Serialization;

    public class CFMessagingSerializer : XmlObjectSerializer
    {
        readonly Type objectType;
        XmlSerializer serializer;

        public CFMessagingSerializer(Type objectType)
            : this(objectType, null, null)
        {
        }
        
        public CFMessagingSerializer(Type objectType, string wrapperName, string wrapperNamespace)
        {
            if (objectType == null)
                throw new ArgumentNullException("objectType");
            if ((wrapperName == null) != (wrapperNamespace == null))
                throw new ArgumentException("wrapperName and wrapperNamespace must be either both null or both non-null.");
            if (wrapperName == string.Empty)
                throw new ArgumentException("Cannot be the empty string.", "wrapperName");
            
            this.objectType = objectType;
            if (wrapperName != null)
            {
                XmlRootAttribute root = new XmlRootAttribute(wrapperName);
                root.Namespace = wrapperNamespace;
                this.serializer = new XmlSerializer(objectType, root);
            }
            else
                this.serializer = new XmlSerializer(objectType);
        }

        public override bool IsStartObject(XmlDictionaryReader reader)
        {
            throw new NotImplementedException();
        }

        public override object ReadObject(XmlDictionaryReader reader, bool verifyObjectName)
        {
            Debug.Assert(serializer != null);
            if (reader == null) throw new ArgumentNullException("reader");
            if (!verifyObjectName)
                throw new NotSupportedException();

            return serializer.Deserialize(reader);
        }
        
        public override void WriteStartObject(XmlDictionaryWriter writer, object graph)
        {
            throw new NotImplementedException();
        }

        public override void WriteObjectContent(XmlDictionaryWriter writer, object graph)
        {
            if (writer == null) throw new ArgumentNullException("writer");
            if (writer.WriteState != WriteState.Element)
                throw new SerializationException(string.Format("WriteState '{0}' not valid. Caller must write start element before serializing in contentOnly mode.",
                    writer.WriteState));
            using (MemoryStream memoryStream = new MemoryStream())
            {
                using (XmlDictionaryWriter bufferWriter = XmlDictionaryWriter.CreateTextWriter(memoryStream, Encoding.UTF8))
                {
                    serializer.Serialize(bufferWriter, graph);
                    bufferWriter.Flush();
                    memoryStream.Position = 0;
                    using (XmlReader reader = new XmlTextReader(memoryStream))
                    {
                        reader.MoveToContent();
                        writer.WriteAttributes(reader, false);
                        if (reader.Read()) // move off start node (we want to skip it)
                        {
                            while (reader.NodeType != XmlNodeType.EndElement) // also skip end node.
                                writer.WriteNode(reader, false); // this will take us to the start of the next child node, or the end node.
                            reader.ReadEndElement(); // not necessary, but clean
                        }
                    }
                }
            }
        }

        public override void WriteEndObject(XmlDictionaryWriter writer)
        {
            throw new NotImplementedException();
        }
        
        public override void WriteObject(XmlDictionaryWriter writer, object graph)
        {
            Debug.Assert(serializer != null);
            if (writer == null) throw new ArgumentNullException("writer");
            serializer.Serialize(writer, graph);
        }
    }
}
Filed under: , ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Mike said on September 15, 2008 7:37 AM:

Andrew, I'm hosting in IIS and using this approach. Over Http I can access the metadata from the mobile client's browser (USA Windows Mobile 5.0 Pocket PC R2 Emulator). But I get the error:-

Channel type 'DemoServiceLibrary.IDemoContract' was requested, but Binding 'BasicHttpBinding' doesn't support it or isn't configured properly to support it.

Parameter name: TChannel

Do you or anyone you may know tried this approach because I haven't found 1 example that covers this scenario. NetCFSvcUtil isn't yet supporting what I want here either.

# D LaPorte said on September 15, 2008 11:48 AM:

Excellent! I'm working on an app to test interop of WS-Reliable messaging and this was perfect for what I needed. Thanks.

# andarno said on September 30, 2008 11:11 PM:

Mike,

Do you get the same error when you browse to your service using a desktop web browser?

Leave a Comment

(required) 
(optional)
(required) 

  
Enter Code Here: Required

Search

This Blog

Interesting blogs

Related sites

Syndication

Page view tracker