How to Implement Ixmlserializable

Proper way to implement IXmlSerializable?

Yes, GetSchema() should return null.

IXmlSerializable.GetSchema Method This
method is reserved and should not be
used. When implementing the
IXmlSerializable interface, you should
return a null reference (Nothing in
Visual Basic) from this method, and instead,
if specifying a custom schema is
required, apply the
XmlSchemaProviderAttribute to the
class.

For both read and write, the object element has already been written, so you don't need to add an outer element in write. For example, you can just start reading/writing attributes in the two.

For write:

The WriteXml implementation you
provide should write out the XML
representation of the object. The
framework writes a wrapper element and
positions the XML writer after its
start. Your implementation may write
its contents, including child
elements. The framework then closes
the wrapper element.

And for read:

The ReadXml method must reconstitute
your object using the information that
was written by the WriteXml method.

When this method is called, the reader
is positioned at the start of the
element that wraps the information for
your type. That is, just before the
start tag that indicates the beginning
of a serialized object. When this
method returns, it must have read the
entire element from beginning to end,
including all of its contents. Unlike
the WriteXml method, the framework
does not handle the wrapper element
automatically. Your implementation
must do so. Failing to observe these
positioning rules may cause code to
generate unexpected runtime exceptions
or corrupt data.

I'll agree that is a little unclear, but it boils down to "it is your job to Read() the end-element tag of the wrapper".

How to implement IXmlSerializable for recursive tags in XML

Why are you wanting to implement IXmlSerializable yourself? You can use various attributes to have the framework do this for you. For example, define a base class (given all your implementations are the same):

public abstract class Widget
{
[XmlIgnore]
public abstract string type { get;}
[XmlAttribute]
public string name { get; set; }
[XmlIgnore]
public string labelCation { get; set; }
[XmlAttribute]
public string text { get; set; }
[XmlAttribute]
public string dbpath { get; set; }
}

The attributes here specify that type and labelCation are ignored (as they don't exist in the XML). The rest are mapped to XML attributes (e.g. name='abc').

You can then create your three sub-types:

public class Textmemo : Widget
{
public override string type { get; } = "CONTROL";
}

public class Label : Widget
{
public override string type { get; } = "LABEL";
}

public class Section : Widget
{
public override string type { get; } = "SECTION";

[XmlElement("textmemo", Type=typeof(Textmemo))]
[XmlElement("label", Type=typeof(Label))]
[XmlElement("section", Type=typeof(Section))]
public List<Widget> subsection { get; } = new List<Widget>();
}

Section is the interesting one as it defines the types permitted as child elements with the relevant name mappings. Similarly to this, you'd then define the root element:

[XmlRoot("dform")]
public class DForm
{
[XmlElement("textmemo", Type=typeof(Textmemo))]
[XmlElement("label", Type=typeof(Label))]
[XmlElement("section", Type=typeof(Section))]
public List<Widget> Widgets { get; } = new List<Widget>();
}

You can see in this demo that your XML round-tripped through the serializer is the same, thus proving this works.

Implementing IXmlSerializable for Classes Containing Collections

Just write a <List> element for the list itself, then loop over the items and write them out as <Item> elements.

If the elements are instances of a class that can be XML Serialized, then you could create an XmlSerializer instance for the type of the element, then just serialize each one to the same XmlWriter you're already using. Example:


public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("XmlSerializable");

writer.WriteElementString("Integer", Integer.ToString());

writer.WriteStartElement("OtherList");
writer.WriteAttributeString("count", OtherList.Count.ToString());

var otherSer = new XmlSerializer(typeof(OtherClass));
foreach (var other in OtherList)
{
otherSer.Serialize(writer, other);
}
writer.WriteEndElement();

writer.WriteEndElement();
}

public void ReadXml(XmlReader reader)
{
reader.ReadStartElement("XmlSerializable");

reader.ReadStartElement("Integer");
Integer = reader.ReadElementContentAsInt();
reader.ReadEndElement();

reader.ReadStartElement("OtherList");
reader.MoveToAttribute("count");
int count = int.Parse(reader.Value);

var otherSer = new XmlSerializer(typeof (OtherClass));
for (int i=0; i<count; i++)
{
var other = (OtherClass) otherSer.Deserialize(reader);
OtherList.Add(other);
}

reader.ReadEndElement();
reader.ReadEndElement();
}

Deserializing collection of types implementing IXmlSerializable runs forever

Your problem is that, as explained in the documentation, ReadXml() must consume its wrapper element as well as its contents:

The ReadXml method must reconstitute your object using the information that was written by the WriteXml method.

When this method is called, the reader is positioned on the start tag that wraps the information for your type. That is, directly on the start tag that indicates the beginning of a serialized object. When this method returns, it must have read the entire element from beginning to end, including all of its contents. Unlike the WriteXml method, the framework does not handle the wrapper element automatically. Your implementation must do so. Failing to observe these positioning rules may cause code to generate unexpected runtime exceptions or corrupt data.

MyClass.ReadXml() is not doing this, which causes an infinite loop when the MyClass object is not serialized as the root element. Instead, your MyClass must look something like this:

public class MyClass : IXmlSerializable
{
public int A { get; set; }
public int B { get; set; }

public XmlSchema GetSchema()
{
return null;
}

public void ReadXml(XmlReader reader)
{
/*
* https://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.readxml.aspx
*
* When this method is called, the reader is positioned at the start of the element that wraps the information for your type.
* That is, just before the start tag that indicates the beginning of a serialized object. When this method returns,
* it must have read the entire element from beginning to end, including all of its contents. Unlike the WriteXml method,
* the framework does not handle the wrapper element automatically. Your implementation must do so. Failing to observe these
* positioning rules may cause code to generate unexpected runtime exceptions or corrupt data.
*/
var isEmptyElement = reader.IsEmptyElement;
this.A = XmlConvert.ToInt32(reader.GetAttribute("A"));
this.B = XmlConvert.ToInt32(reader.GetAttribute("B"));
reader.ReadStartElement();
if (!isEmptyElement)
{
reader.ReadEndElement();
}
}

public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("A", XmlConvert.ToString(this.A));
writer.WriteAttributeString("B", XmlConvert.ToString(this.B));
}
}

Now your <MyClass> element is very simple with no nested or optional elements. For more complex custom serializations there are a couple of strategies you could adopt to guarantee that your ReadXml() method reads exactly as much as it should, no more and no less.

Firstly, you could call XNode.ReadFrom() to load the current element into an XElement. This requires a bit more memory than parsing directly from an XmlReader but is much easier to work with:

public class MyClass : IXmlSerializable
{
public int A { get; set; }
public int B { get; set; }

public XmlSchema GetSchema()
{
return null;
}

public void ReadXml(XmlReader reader)
{
var element = (XElement)XNode.ReadFrom(reader);
this.A = (int)element.Attribute("A");
this.B = (int)element.Attribute("B");
}

public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("A", XmlConvert.ToString(this.A));
writer.WriteAttributeString("B", XmlConvert.ToString(this.B));
}
}

Secondly, you could use XmlReader.ReadSubtree() to ensure the required XML content is consumed:

public class MyClass : IXmlSerializable
{
public int A { get; set; }
public int B { get; set; }

public XmlSchema GetSchema()
{
return null;
}

protected virtual void ReadXmlSubtree(XmlReader reader)
{
this.A = XmlConvert.ToInt32(reader.GetAttribute("A"));
this.B = XmlConvert.ToInt32(reader.GetAttribute("B"));
}

public void ReadXml(XmlReader reader)
{
// Consume all child nodes of the current element using ReadSubtree()
using (var subReader = reader.ReadSubtree())
{
subReader.MoveToContent();
ReadXmlSubtree(subReader);
}
reader.Read(); // Consume the end element itself.
}

public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("A", XmlConvert.ToString(this.A));
writer.WriteAttributeString("B", XmlConvert.ToString(this.B));
}
}

A few final notes:

  • Be sure to handle both <MyClass /> and <MyClass></MyClass>. These two forms are semantically identical and a sending system could chose either.

  • Prefer the methods from the XmlConvert class to convert primitives from and to XML. Doing so handles internationalization correctly.

  • Be sure to test with and without indentation. Sometimes a ReadXml() method will consume an extra XML node but the bug will be hidden when indentation is enabled -- as it is the whitespace node that gets eaten.

  • For further reading see How to Implement IXmlSerializable Correctly.

How to implement IXmlSerializable.ReadXml in case of using AbstractFactory-Pattern and Polymorphism

OK, after thinking for a bit more I came up with the solution myself. Quite easy once you get there ;-)

Since I'm using the Factory Pattern it's the Factory that needs to implement the deserialization. This is a creational pattern after all. This means, that ALL the creation methods should go into that factory. And deserializing is a creation method.

All i need to do is pass the XmlReader object to the factory and expect a return of whatever the Factory creates.

To stay with the above code example:

Public Interface IAnimalFactory
Public Function Breed(animalType as AnimalType) as IAnimal
Public Function XmlDeserializeAnimal(reader As XmlReader) As IAnimal
End Interface

Public Class AnimalFactoryImpl
Implements IAnimalFactory
Public Function Breed(animalType as AnimalType) as IAnimal
Select Case animalType
case ...
return new Dog()
End Select
End Function

Public Function XmlDeserializeAnimal(reader As XmlReader) As IAnimal implements IAnimalFactory.XmlDeserializeAnimal
'read all the tags here inkl. animalType
Dim returnAnimal as IAnimal = Me.Breed(animalType)
'set name and age here as per xml
Return returnAnimal
End Class

Now this can easily be called from withing the container object (e.g. Farm) which also implements IXmlSerializable.
And all the container class needs to know of is the IAnimalFactory and the XmlDeserializeAnimal method.

Rather straight forward if you think about it (^_~)

Best way to implement IXmlSerializable ReadXml() using XPath

I suspect you might run into some performance issues if using that approach routinely. Since your xml is relatively simple, I strongly suspect that you would do better just using XmlReader directly...

...but doing so isn't easy; IMO, it is better to try to avoid the need to implement IXmlSerializable (juts using regular collection properties etc) - it is a common cause of bugs and frustration.



Related Topics



Leave a reply



Submit