1

I am trying to make a soap call and deserialize the answer I receive back. The soap call is made correctly but I don't manage so far to deserialize the answer into an object. Specifically speaking, the XML contained in the answer is an array of repeated data. As you can guess I want to create an array of objects out of it. I have checked other similar questions but so far none worked. Let's start by defining the whole XML.

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
  <s:Header>
    <a:Action s:mustUnderstand="1">ThisIsATry/RetrieveResponse</a:Action>
  </s:Header>
  <s:Body>
    <RetrieveResponse xmlns="ThisIsATry">
      <RetrieveInsurersResult xmlns:b="http://schemas.datacontract.org/2004/07/ThisIsATry" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <b:Errors xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
        <b:Message>Selected 2 records</b:Message>
        <b:Results xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
          <c:ArrayOfKeyValueOfstringstring>
            <c:KeyValueOfstringstring>
              <c:Key>PersonId</c:Key>
              <c:Value>1</c:Value>
            </c:KeyValueOfstringstring>
            <c:KeyValueOfstringstring>
              <c:Key>Name</c:Key>
              <c:Value>Mike</c:Value>
            </c:KeyValueOfstringstring>
          </c:ArrayOfKeyValueOfstringstring>
          <c:ArrayOfKeyValueOfstringstring>
            <c:KeyValueOfstringstring>
              <c:Key>PersonId</c:Key>
              <c:Value>2</c:Value>
            </c:KeyValueOfstringstring>
            <c:KeyValueOfstringstring>
              <c:Key>Name</c:Key>
              <c:Value>Henry</c:Value>
            </c:KeyValueOfstringstring>
          </c:ArrayOfKeyValueOfstringstring>
        </b:Results>
        <b:Warnings xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
      </RetrieveInsurersResult>
    </RetrieveResponse>
  </s:Body>
</s:Envelope>

The part I need to use is:

    <b:Results xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
      <c:ArrayOfKeyValueOfstringstring>
        <c:KeyValueOfstringstring>
          <c:Key>PersonId</c:Key>
          <c:Value>1</c:Value>
        </c:KeyValueOfstringstring>
        <c:KeyValueOfstringstring>
          <c:Key>Name</c:Key>
          <c:Value>Mike</c:Value>
        </c:KeyValueOfstringstring>
      </c:ArrayOfKeyValueOfstringstring>
      <c:ArrayOfKeyValueOfstringstring>
        <c:KeyValueOfstringstring>
          <c:Key>PersonId</c:Key>
          <c:Value>2</c:Value>
        </c:KeyValueOfstringstring>
        <c:KeyValueOfstringstring>
          <c:Key>Name</c:Key>
          <c:Value>Henry</c:Value>
        </c:KeyValueOfstringstring>
      </c:ArrayOfKeyValueOfstringstring>
    </b:Results>

As you can see there are 2 "objects" of type c:ArrayOfKeyValueOfstringstring>. Each object contains 2 properties of type c:KeyValueOfstringstring . Finally, each one of these properties contains a key and a value . What I need is an array of c:ArrayOfKeyValueOfstringstring, containing an array of c:KeyValueOfstringstring and the relative informations. I tried to represent this data in my c# code with the following classes:

public class ArrayOfKeyValueOfstringstring
{
    [XmlElement("ArrayOfKeyValueOfstringstring")]
    public KeyValueOfstringstring[] Value { get; set; }

}

public class KeyValueOfstringstring
{
    [XmlElement("KeyValueOfstringstring")]
    public KeyValue Pair { get; set; }

}

public class KeyValue
{
    [XmlElement("Key")]
    public string Key { get; set; }
    [XmlElement("Value")] 
    public string Value { get; set; }
}

The way I deal with the response so far is:

var result = client.UploadString(dataBaseConnectionString, soapTemplate); //calls the serive and brings back the XML
var document = XDocument.Parse(result); //gives back the first XML I posted in this question
var results = document.Descendants().FirstOrDefault(x => x.Name.LocalName == "Results"); //gives back the 2nd XML I posted

xmlNamespaceManager.AddNamespace("c", "http://schemas.microsoft.com/2003/10/Serialization/Arrays");
var oXmlSerializer = new XmlSerializer(typeof(SoapResponse[]));

//this part is wrong..
using (var mem = new MemoryStream(Encoding.UTF8.GetBytes(results.ToString())))
{
      var responseObj = (ArrayOfKeyValueOfstringstring)oXmlSerializer.Deserialize(mem);
}

Thanks in advance for the help!

2

2 Answers 2

1

As it so happens, ArrayOfKeyValueOfstringstring is the element name that DataContractSerializer chooses when serializing a Dictionary<string, string>, so you're going to have a much easier time using that serializer to deserialize your XML.

First, introduce the following extension method:

public static partial class DataContractSerializerExtensions
{
    public static T ToContractObject<T>(this XContainer doc, DataContractSerializer serializer = null)
    {
        if (doc == null)
            throw new ArgumentNullException();
        using (var reader = doc.CreateReader())
        {
            return (T)(serializer ?? new DataContractSerializer(typeof(T))).ReadObject(reader);
        }
    }
}

And now you can parse your XML into a List<Dictionary<string, string>> as follows:

var dictionaries = document.Descendants()
    .Where(d => d.Name.LocalName == "ArrayOfKeyValueOfstringstring")
    .Select(d => d.ToContractObject<Dictionary<string, string>>())
    .ToList();

Then later you can map the list of dictionaries to your preferred model.

However, if for whatever reason you must use XmlSerializer, instead introduce the following extension method and data model:

public static partial class XmlSerializerExtensions
{
    public static T ToObject<T>(this XContainer doc, XmlSerializer serializer = null)
    {
        if (doc == null)
            throw new ArgumentNullException();
        using (var reader = doc.CreateReader())
        {
            return (T)(serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
        }
    }
}

[XmlRoot(ElementName = "KeyValueOfstringstring", Namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
public class KeyValueOfstringstring
{
    [XmlElement(ElementName = "Key", Namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
    public string Key { get; set; }
    [XmlElement(ElementName = "Value", Namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
    public string Value { get; set; }
}

[XmlRoot(ElementName = "ArrayOfKeyValueOfstringstring", Namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
public class ArrayOfKeyValueOfstringstring
{
    [XmlElement(ElementName = "KeyValueOfstringstring", Namespace = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]
    public List<KeyValueOfstringstring> KeyValueOfstringstring { get; set; }
}

And deserialize as follows:

var results = document.Descendants()
    .Where(d => d.Name.LocalName == "ArrayOfKeyValueOfstringstring")
    .Select(d => d.ToObject<ArrayOfKeyValueOfstringstring>())
    .ToList();

Notes:

  • I use XNode.CreateReader() to return an XmlReader from which an XNode can be deserialized directly. This avoids the requirement to convert selected node(s) back to a string representation and then re-parse.

  • The namespace for the <b:Results> node is "http://schemas.datacontract.org/2004/07/ThisIsATry". This namespace feels... provisional.. so I avoided using it in my answer.

All this being said, since the XML looks like it might have been generated by a wcf service, is there any chance the service provides WSDL Metadata? If so you may be able to generate a client automatically. See

For documentation on how this can be done.

Sign up to request clarification or add additional context in comments.

3 Comments

It's some very old legacy code that I cannot touch in order to use a client.. but your solution is amazing! You explained everything step by step and it works perfectly.. thank you big time for your help!
I need exact opposite of this.. I need to serialize an array of object inside an object using C#. Can any one guide me on it... Any link or documentation would help. Thanks
@Renascent - you should probably ask a new question (with a minimal reproducible example) as the format for questions here is one question per post - the answers to which should answer that specific question.
0

Try xml linq

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            XDocument doc = XDocument.Load(FILENAME);

            XElement results = doc.Descendants().Where(x => x.Name.LocalName == "Results").FirstOrDefault();
            XNamespace nsC = results.GetNamespaceOfPrefix("c");

            Dictionary<string, List<string>> dict = results.Descendants(nsC + "KeyValueOfstringstring")
               .GroupBy(x => (string)x.Element(nsC + "Key"), y => (string)y.Element(nsC + "Value"))
               .ToDictionary(x => x.Key, y => y.ToList());
        }
    }

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.