Search code examples
c#wcfserializationdeserialization

WCF Serialize Array or Collection Without a Container


I have a situation where we are connecting to a SOAP service.

The response we get back looks something like this:

<SomeObject>
    <item1>1</item1>
    <thing1>2</thing1>
    <arrayItem><foo>text</foo></arrayItem>
    <arrayItem><foo>text1</foo></arrayItem>
    <arrayItem><foo>text2</foo></arrayItem>
</SomeObject>

I need to replicate the output of that response. The issue I keep running into is that the <arrayItem>'s are encapsulated by <arrayItemList> and I really need the <arrayItemList> to go away.

Does anyone know what I could put on my WCF objects to properly serialize/deserialize the objects we are receiving?

EDIT

The object I am working with is something like this:

[DataContract]
public class SomeObject
{
    [DataMember(Order = 0)]
    public string item1 {get;set;}

    [DataMember(Order = 1)]
    public string thing1 {get;set;}

    [DataMember(Order = 2)]
    public List<arrayItem> {get;set;}
}

[DataContract]
public class arrayItem
{
    [DataMember]
    public string foo {get;set;}
}

Solution

  • Unfortunately I was unable to find a great solution to this. I did, however, find a workable solution.

    Warning - If at all possible you should try to get the WSDL modified to prevent the need for this solution. This is more of a hack then a suggested solution but will work in a pinch.

    The solution was to implement IClientMessageInspector and IEndpointBehavior. These interfaces allow access the raw text request and response. These allowed the modification of the messages before they are either sent to the service or deserialized by WCF. Below is my Implementation and a custom class that allowed for the modification I needed to the message.

    public class MyService : IClientMessageInspector
    {
        public void DoWork()
        {
             // Do some stuff
        }
    
        public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            string message = reply.ToString();
    
            // Load the reply message in DOM for easier modification
            XmlDocument doc = new XmlDocument();
            doc.Load(reply.GetReaderAtBodyContents());
    
            // Perform the modification
            MessageHelper.FixArrays(doc);
    
            // Create New Message
            XmlNodeReader reader = new XmlNodeReader(doc.DocumentElement);
            Message newMsg = Message.CreateMessage(reply.Version, reply.Headers.Action, reader);
    
            // Preserve the headers of the original message
            if (reply.Headers.Any())
                newMsg.Headers.CopyHeaderFrom(reply, 0);
    
            foreach (string propertyKey in reply.Properties.Keys)
            {
                newMsg.Properties.Add(propertyKey, reply.Properties[propertyKey]);
            }
    
            // Close the original message and return new message
            reply.Close();
            reply = newMsg;
        }
    
        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
        {
            return null;
        }
    }
    
    public static class MessageHelper
    {
        public static void FixArrays(XmlDocument doc)
        {
            // Arrearage
            WrapElement(doc, "foo", "arrayItem", "http://url.com/namespace/foo");
        }
    
        private static void WrapElement(XmlDocument doc, string elementName, string wrapperName, string nameSpace)
        {
            var names = new XmlNamespaceManager(doc.NameTable);
            names.AddNamespace("a", nameSpace);
    
            var Nodes = doc.SelectNodes("//a:" + elementName, names);
    
            if (Nodes.Count > 0)
            {
                var newBorrower = doc.CreateElement(Nodes.Item(0).Prefix, wrapperName, Nodes.Item(0).NamespaceURI);
    
                foreach (XmlElement node in Nodes)
                {
                    newBorrower.AppendChild(node.Clone());
                }
    
                Nodes.Item(0).ParentNode.ReplaceChild(newBorrower, Nodes.Item(0));
    
                for (int i = 1; i <= Nodes.Count - 1; i++)
                {
                    Nodes.Item(i).ParentNode.RemoveChild(Nodes.Item(i));
                }
            }
        }
    
        private static void WrapRenameElement(XmlDocument doc, string newName, string elementName, string wrapperName, string nameSpace, string newNamespace)
        {
            var names = new XmlNamespaceManager(doc.NameTable);
            names.AddNamespace("a", nameSpace);
            names.AddNamespace("b", newNamespace);
    
            var Nodes = doc.SelectNodes("//a:" + elementName + "/..", names);
    
            foreach (XmlElement parent in Nodes)
            {
                var newBorrower = doc.CreateElement(parent.Prefix, wrapperName, parent.NamespaceURI);
    
                foreach (XmlElement child in parent.ChildNodes)
                {
                    if (child.LocalName == elementName)
                    {
                        var newNode = RenameNode(child.Clone(), newNamespace, newName);
                        parent.RemoveChild(child);
                        newBorrower.AppendChild(newNode);
                    }
                }
    
                if (newBorrower.ChildNodes.Count > 0)
                    parent.AppendChild(newBorrower);
            }
        }
    
        private static void WrapRenameElement(XmlDocument doc, string newName, string elementName, string wrapperName, string nameSpace)
        {
            var names = new XmlNamespaceManager(doc.NameTable);
            names.AddNamespace("a", nameSpace);
    
            var Nodes = doc.SelectNodes("//a:" + elementName + "/..", names);
    
            foreach (XmlElement parent in Nodes)
            {
                var newBorrower = doc.CreateElement(parent.Prefix, wrapperName, parent.NamespaceURI);
    
                foreach (XmlElement child in parent.ChildNodes)
                {
                    if (child.LocalName == elementName)
                    {
                        var newNode = RenameNode(child.Clone(), nameSpace, newName);
                        parent.RemoveChild(child);
                        newBorrower.AppendChild(newNode);
                    }
                }
    
                if (newBorrower.ChildNodes.Count > 0)
                    parent.AppendChild(newBorrower);
            }
        }
    
        public static XmlNode RenameNode(XmlNode node, string namespaceURI, string qualifiedName)
        {
            if (node.NodeType == XmlNodeType.Element)
            {
                XmlElement oldElement = (XmlElement)node;
                XmlElement newElement =
                node.OwnerDocument.CreateElement(qualifiedName, namespaceURI);
    
                while (oldElement.HasAttributes)
                {
                    newElement.SetAttributeNode(oldElement.RemoveAttributeNode(oldElement.Attributes[0]));
                }
    
                while (oldElement.HasChildNodes)
                {
                    newElement.AppendChild(oldElement.FirstChild);
                }
    
                if (oldElement.ParentNode != null)
                {
                    oldElement.ParentNode.ReplaceChild(newElement, oldElement);
                }
    
                return newElement;
            }
            else
            {
                return null;
            }
        }
    }
    

    Like I said, It's not pretty and it's essentially a hack but this solution will work for the problem I had. I hope no one else has to deal with this, but if they do, I hope this will help.