CustomXmlObjectSerializer

Posted on November 24, 2010 by

1


In this post I’m going to describe a scenario which we encountered when we had to work with an external partner that expected us to deliver messages in a very specific format and with specific headers.

Our first approach was to add the “XmlSerializerFormat” attribute on each operation on the contract, and to add the “MessageContract” attribute on the serialized class delivered in each operation (this was supposed to give us control on which parameters to pass as headers, and which ones in the body).

Our code seemed like this:

[ServiceContract]
public interface Test
{
    [XmlSerializerFormat]
    [OperationContract]
    MsgResponse Foo(MsgRequest request);
}

[MessageContract]
[Serializable]
public class MsgRequest
{
    [MessageHeader]
    public String MyHeader { get; set; }

     [MessageBodyMember]
     public String MyVal { get; set; }

     [System.Xml.Serialization.XmlIgnoreAttribute()]
     public bool TimeStampSpecified { get; set; }

     [MessageBodyMember]
     public System.DateTime TimeStamp { get; set; }
}

The problem we encountered was that what we expected was not what we got….

Although we instruct WCF to use the XmlSerializer, we found out that when it is used  together with the  “MessageContract” attribute, it acts a little bit differently, and WCF ignores some of the xml serialization attributes (for example, it serialized the TimeStamp in the sample below even though it has the XmlIgnoreAttribute)

When we removed the “MessageContract” attribute, the body was serialized as expected according to the defined attributes. (And in our example below- without the TimeStamp field but also without the headers…. since we removed the “MessageContract” attribute….)

You can see in the following code snippet how the message body looked with and without the “MessageContract” attribute.

With “MessageContract”:

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
   <h:MyHeader xmlns="http://tempuri.org/" xmlns:h="http://tempuri.org/">SubmitReq</h:MyHeader>
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    < MsgRequest xmlns="http://tempuri.org/">
      <MyStatus>running</MyStatus>
      <MyVal>Test</MyVal>
      <TimeStamp>0001-01-01T00:00:00</TimeStamp>
    </ MsgRequest>
   </s:Body>
 </s:Envelope>

without “MessageContract”:


<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

http://www.w3.org/2001/XMLSchema">
     < MsgRequest xmlns="http://tempuri.org/">
       <message>
         <MyHeader>SubmitReq</MyHeader>
         <MyVal>Extended Swa Sample Client</MyVal>
         <MyStatus>Ecf running</MyStatus>
      </message>
     </ MsgRequest >
   </s:Body>
 </s:Envelope>

The solution for this problem was to discard the “MessageContract” attribute, and to add the headers, and the body separately to the Soap message.

The soap body was added using the Message.CreateMessage with the following parameters:

public static Message CreateMessage(MessageVersion version, string action, object body, XmlObjectSerializer serializer);

For that purpose we had to define a CustomXmlObjectSerializer which inherits from XmlObjectSerializer, and uses internally the XmlSerializer.

public class CustomXmlObjectSerializer : XmlObjectSerializer
    {
 
        private XmlSerializer m_serializer;
        string m_rootName;
        string m_rootNamespace;
 
        public CustomXmlObjectSerializer(Type typeToSerialize)
        {
            Initialize(typeToSerialize, null, null);
        }
 
        public CustomXmlObjectSerializer(
            Type typeToSerialize,
            XmlQualifiedName qualifiedName)
        {
            if (qualifiedName == null)
            {
                throw new ArgumentException("qualifiedName");
            }
            Initialize(typeToSerialize, qualifiedName.Name, qualifiedName.Namespace);
        }
 
        void Initialize(Type type, string rootName, string rootNamespace)
        {
            if (type == null)
            {
                throw new ArgumentException("type");
            }
            m_rootName = rootName;
            m_rootNamespace = rootNamespace ?? string.Empty;
            if (m_rootName == null)
            {
                m_serializer = new XmlSerializer(type);
            }
            else
            {
                var xmlRoot = new XmlRootAttribute 
                { 
                    ElementName = m_rootName,
                    Namespace = m_rootNamespace 
                };
                m_serializer = new XmlSerializer(type, xmlRoot);
            }
        }
 
       public override bool IsStartObject(XmlDictionaryReader reader)
       {
           if (reader == null)
           {
               throw new ArgumentException("reader");
           }
           reader.MoveToElement();
           if (m_rootName != null)
           {
               return reader.IsStartElement(m_rootName, m_rootNamespace);
           }
           return reader.IsStartElement();
        }
 
        public override object ReadObject(
            XmlDictionaryReader reader,
            bool verifyObjectName)
        {
            if (reader == null)
            {
                throw new ArgumentException("reader");
            }
            return m_serializer.Deserialize(reader);
        }
 
        public override void WriteStartObject(
            XmlDictionaryWriter writer,
            object graph)
        {
            throw new NotSupportedException();
        }
 
        public override void WriteObjectContent(
            XmlDictionaryWriter writer,
            object graph)
        {
            if (writer == null)
            {
                throw new ArgumentException("writer");
            }
            if (writer.WriteState != WriteState.Element)
            {
                throw new ArgumentException(String.Format(
                        "WriteState '{0}' is in-valid.",writer.WriteState));
            }
            m_serializer.Serialize(writer, graph);
        }
 
        public override void WriteEndObject(XmlDictionaryWriter writer)
        {
            throw new NotSupportedException();
        }
 
        public override void WriteObject(
            XmlDictionaryWriter writer, 
            object graph)
        {
            if (writer == null)
            {
                throw new ArgumentException("writer");
            }
            m_serializer.Serialize(writer, graph);
        }
    }

Advertisements
Posted in: General