The reason that your properties are not deserialized is explained in the documentation section Serializing a Class that Implements the ICollection Interface:
You can create your own collection classes by implementing the ICollection interface, and use the XmlSerializer to serialize instances of these classes. Note that when a class implements the ICollection interface, only the collection contained by the class is serialized. Any public properties or fields added to the class will not be serialized.
So, that's that.
You might consider changing your design so your classes do not have properties. For some reasons to make this change, see Why not inherit from List?.
If you nevertheless choose to have a collection with serializable properties, you're going to need to manually implement IXmlSerializable
. This is burdensome, since you need to handle many "edge" cases including empty elements, unexpected elements, comments, and presence or absence of whitespace, all of which can throw off your ReadXml()
method. For some background, see How to Implement IXmlSerializable Correctly.
First, create a base class for generic lists with serializable properties:
public class XmlSerializableList<T> : List<T>, IXmlSerializable where T : new()
{
public XmlSerializableList() : base() { }
public XmlSerializableList(IEnumerable<T> collection) : base(collection) { }
public XmlSerializableList(int capacity) : base(capacity) { }
#region IXmlSerializable Members
const string CollectionItemsName = "Items";
const string CollectionPropertiesName = "Properties";
void IXmlSerializable.WriteXml(XmlWriter writer)
{
// Do not write the wrapper element.
// Serialize the collection.
WriteCollectionElements(writer);
// Serialize custom properties.
writer.WriteStartElement(CollectionPropertiesName);
WriteCustomElements(writer);
writer.WriteEndElement();
// Do not end the wrapper element.
}
private void WriteCollectionElements(XmlWriter writer)
{
if (Count < 1)
return;
// Serialize the collection.
writer.WriteStartElement(CollectionItemsName);
var serializer = new XmlSerializer(typeof(T));
var ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
foreach (var item in this)
{
serializer.Serialize(writer, item, ns);
}
writer.WriteEndElement();
}
/// <summary>
/// Write ALL custom elements to the XmlReader
/// </summary>
/// <param name="writer"></param>
protected virtual void WriteCustomElements(XmlWriter writer)
{
}
void IXmlSerializable.ReadXml(XmlReader reader)
{
if (reader.IsEmptyElement)
{
reader.Read();
return;
}
reader.ReadStartElement(); // Advance to the first sub element of the wrapper element.
while (reader.NodeType != XmlNodeType.EndElement)
{
if (reader.NodeType != XmlNodeType.Element)
// Comment, whitespace
reader.Read();
else if (reader.IsEmptyElement)
reader.Read();
else if (reader.Name == CollectionItemsName)
ReadCollectionElements(reader);
else if (reader.Name == CollectionPropertiesName)
ReadCustomElements(reader);
else
// Unknown element, skip it.
reader.Skip();
}
// Move past the end of the wrapper element
reader.ReadEndElement();
}
void ReadCustomElements(XmlReader reader)
{
reader.ReadStartElement(); // Advance to the first sub element of the collection element.
while (reader.NodeType != XmlNodeType.EndElement)
{
if (reader.NodeType == XmlNodeType.Element)
{
using (var subReader = reader.ReadSubtree())
{
while (subReader.NodeType != XmlNodeType.Element) // Read past XmlNodeType.None
if (!subReader.Read())
break;
ReadCustomElement(subReader);
}
}
reader.Read();
}
// Move past the end of the properties element
reader.Read();
}
void ReadCollectionElements(XmlReader reader)
{
var serializer = new XmlSerializer(typeof(T));
reader.ReadStartElement(); // Advance to the first sub element of the collection element.
while (reader.NodeType != XmlNodeType.EndElement)
{
if (reader.NodeType == XmlNodeType.Element)
{
using (var subReader = reader.ReadSubtree())
{
while (subReader.NodeType != XmlNodeType.Element) // Read past XmlNodeType.None
if (!subReader.Read())
break;
var item = (T)serializer.Deserialize(subReader);
Add(item);
}
}
reader.Read();
}
// Move past the end of the collection element
reader.Read();
}
/// <summary>
/// Read ONE custom element from the XmlReader
/// </summary>
/// <param name="reader"></param>
protected virtual void ReadCustomElement(XmlReader reader)
{
}
XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
#endregion
}
To use this class, you will need to override ReadCustomElement(XmlReader reader)
, which reads a single custom property, and WriteCustomElements(XmlWriter writer)
, which writes all custom properties. (Note the asymmetry, it makes implementing class hierarchies a little easier.) Then create your Porudzbina
class as follows:
public class Porudzbina : XmlSerializableList<PorudzbenicaStavka>
{
public long KomSifra { get; set; }
public Guid KomId { get; set; }
const string KomSifraName = "KomSifra";
const string KomIdName = "KomId";
protected override void WriteCustomElements(XmlWriter writer)
{
writer.WriteElementString(KomSifraName, XmlConvert.ToString(KomSifra));
writer.WriteElementString(KomIdName, XmlConvert.ToString(KomId));
base.WriteCustomElements(writer);
}
protected override void ReadCustomElement(XmlReader reader)
{
if (reader.Name == KomSifraName)
{
KomSifra = reader.ReadElementContentAsLong();
}
else if (reader.Name == KomIdName)
{
var s = reader.ReadElementContentAsString();
KomId = XmlConvert.ToGuid(s);
}
else
{
base.ReadCustomElement(reader);
}
}
}
This will create XML that looks like:
<Porudzbina>
<Items>
<PorudzbenicaStavka>
<!-- contents of first PorudzbenicaStavka -->
</PorudzbenicaStavka>
<!-- Additional PorudzbenicaStavka -->
</Items>
<Properties>
<KomSifra>101</KomSifra>
<KomId>bb23a3b8-23d3-4edd-848b-d7621e6ed2c0</KomId>
</Properties>
</Porudzbina>