1

( See this demo for more details )

Suppose I have an XML file like this:

<?xml version="1.0" encoding="utf-16"?>
<Container>
  <SomeValue>1</SomeValue>
  <Item>
    <Containers>
      <Container>
        <SomeValue>2</SomeValue>
          <Item>
            <!-- etc... -->
          </Item>
      </Container>
      <Container>
        <SomeValue>3</SomeValue>
      </Container>
    </Containers>
  </Item>
</Container>

where the class called Container contains a single Item and the class called Item can have many sub-containers.

The Container class implements IXmlSerializable (see notes for why), and it has the following method:

public void ReadXml(XmlReader reader)
{
    XElement root = XElement.Load(reader.ReadSubtree());
    this.SomeValue = (int)root.Element("SomeValue");

    XElement itemElt = root.Element("Item");
    if (itemElt == null)
    {
        // Empty container
        return;
    }

    XmlSerializer xmlSerializer = new XmlSerializer(typeof(Item));
    using (var itemReader = itemElt.CreateReader())
    {
        this.Item = (Item)xmlSerializer.Deserialize(itemReader);
    }
}

However, running this results in the root container's item only containing the first container (where SomeValue is 2) even though there is more than 1 container in its Containers node.


So why are the other containers being skipped and how could I fix it?


I'm assuming it's an issue with the call to ReadSubtree() but leaving this out gives me:

InvalidOperationException: The XmlReader state should be EndOfFile after this operation.

Some Notes:

The reason I have to use IXmlSerializable is because the actual "Item" type is fetched from a database depending on the the element's name. So when I call new XmlSerializer(typeof(Item)), I'm actually calling new XmlSerializer(specificItemType).

Additionally, I would prefer to do as much as I can with System.Xml.Linq since it's very readable (and I'm pretty new to XML stuff). It also allows me to get data from anywhere in the tree without having to re-read the whole document, which I do a bit in my actual code. However, if it must go then I'm willing to part with it.

MrGVSV
  • 127
  • 2
  • 7

1 Answers1

2

The issue is that this:

XElement root = XElement.Load(reader.ReadSubtree());

Leaves the reader at the end of the element (i.e. </Container>). The contract for IXmlSerializable.ReadXml is that you must 'read' this too, leaving the reader at the beginning of the next element. This answer helps to clarify that.

So the fix is to add another reader.Read() call before returning. I've updated your demo:

===Input===
Container {1} - Item: [ Container {2} - null, Container {3} - null ] (2)

===Output===
Container {1} - Item: [ Container {2} - null, Container {3} - null ] (2)
Charles Mager
  • 25,735
  • 2
  • 35
  • 45