c# inheriting generic collection and serialization - c#

The setup:
class Item
{
private int _value;
public Item()
{
_value = 0;
}
public int Value { get { return _value; } set { _value = value; } }
}
class ItemCollection : Collection<Item>
{
private string _name;
public ItemCollection()
{
_name = string.Empty;
}
public string Name { get {return _name;} set {_name = value;} }
}
Now, trying to serialize using the following code fragment:
ItemCollection items = new ItemCollection();
...
XmlSerializer serializer = new XmlSerializer(typeof(ItemCollection));
using (FileStream f = File.Create(fileName))
serializer.Serialize(f, items);
Upon looking at the resulting XML I see that the ItemCollection.Name value is not there!
I think what may be happening is that the serializer sees the ItemCollection type as a simple Collection thus ignoring any other added properties...
Is there anyone having encountered such a problem and found a solution?
Regards,
Stécy

This behavior is "By Design". When deriving from a collection class the Xml Seralizier will only serialize the collection elements. To work around this you should create a class that encapsulates the collection and the name and have that serialized.
class Wrapper
{
private Collection<Item> _items;
private string _name;
public Collection<Item> Items { get {return _items; } set { _items = value; } }
public string Name { get { return _name; } set { _name = value; } }
}
A detailed discussion is available here: http://blogs.vertigo.com/personal/chris/Blog/archive/2008/02/01/xml-serializing-a-derived-collection.aspx

XmlSerializer is evil. That said, any object that implements IEnumerable will be serialized as an simple collection, ignoring any extra properties you've added yourself.
You will need to create a new class that holds both your property and a property that returns the collection.

I am not sure if I am missing something, but do you want the resulting xml to be
<ItemCollection>
<Name>name val</Name>
<Item>
<Value>1</alue>
</Item
<Item>
<Value>2</alue>
</Item
</ItemCollection>
If so, just apply the XmlRoot attribute to the itemcollection class and set the element name...
[XmlRoot(ElementName="ItemCollection")]
public class ItemCollection : Collection<Item>
{
[XmlElement(ElementName="Name")]
public string Name {get;set;}
}
This will instruct the serializer to output the required name for you collection container.

You can also try to implelemnt your own serialization using IXmlSerializable interface
public class ItemCollection : Collection<Item>,IXmlSerializable
{
private string _name;
public ItemCollection()
{
_name = string.Empty;
}
public string Name
{
get { return _name; }
set { _name = value; }
}
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteElementString("name", _name);
List<Item> coll = new List<Item>(this.Items);
XmlSerializer serializer = new XmlSerializer(coll.GetType());
serializer.Serialize(writer, coll);
}
#endregion
}
Above code will generate the serialized xml as
<?xml version="1.0"?>
<ItemCollection>
<name />
<ArrayOfItem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Item>
<Value>1</Value>
</Item>
<Item>
<Value>2</Value>
</Item>
</ArrayOfItem>
</ItemCollection>

public class Animals : List<Animal>, IXmlSerializable
{
private static Type[] _animalTypes;//for IXmlSerializable
public Animals()
{
_animalTypes = GetAnimalTypes().ToArray();//for IXmlSerializable
}
// this static make you access to the same Animals instance in any other class.
private static Animals _animals = new Animals();
public static Animals animals
{
get {return _animals; }
set { _animals = value; }
}
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
bool wasEmpty = reader.IsEmptyElement;
reader.Read();
if (wasEmpty)
return;
reader.MoveToContent();
reader.ReadStartElement("Animals");
// you MUST deserialize with 'List<Animal>', if Animals class has no 'List<Animal>' fields but has been derived from 'List<Animal>'.
List<Animal> coll = GenericSerializer.Deserialize<List<Animal>>(reader, _animalTypes);
// And then, You can set 'Animals' to 'List<Animal>'.
_animals.AddRange(coll);
reader.ReadEndElement();
//Read Closing Element
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement("Animals");
// You change 'List<Animal>' to 'Animals' at first.
List<Animal> coll = new List<Animal>(_animals);
// And then, You can serialize 'Animals' with 'List<Animal>'.
GenericSerializer.Serialize<List<Animal>>(coll, writer, _animalTypes);
writer.WriteEndElement();
}
#endregion
public static List<Type> GetAnimalTypes()
{
List<Type> types = new List<Type>();
Assembly asm = typeof(Animals).Assembly;
Type tAnimal = typeof(Animal);
//Query our types. We could also load any other assemblies and
//query them for any types that inherit from Animal
foreach (Type currType in asm.GetTypes())
{
if (!currType.IsAbstract
&& !currType.IsInterface
&& tAnimal.IsAssignableFrom(currType))
types.Add(currType);
}
return types;
}
}

Related

C# XML Serialization XMLElement Path

i am currently working on a project where i need the c# Xml Serialization.
My Problem:
I havea class called ClassA. It has a Name Property which i would like to serialize in the Xml-File.
public class ClassA : BaseClass
{
[XmlElement("name")]
public string Name
{
get { return GetProperty(() => Name); }
set { SetProperty(() => Name, value); }
}
}
So when i serialize this with this serializer
public class PetriNetXMLReader
{
public void SaveToXML(PetriNetXML petriNet, string fileName)
{
System.Xml.Serialization.XmlSerializer writer =
new System.Xml.Serialization.XmlSerializer(typeof(PetriNetXML));
System.IO.FileStream file = System.IO.File.Create(fileName);
writer.Serialize(file, petriNet);
file.Close();
}
public PetriNetXML ReadFromXML(string fileName)
{
var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
XmlSerializer deserializer = new XmlSerializer(typeof(PetriNetXML));
return (PetriNetXML)deserializer.Deserialize(fileStream);
}
}
i get a xml-file like this
<ClassA Id="5eda8e4c-0698-4e07-9d20-7985964786f9" >
<name>Description</name>
</ClassA>
So my Question:
I would like to have a xml like
<ClassA Id="5eda8e4c-0698-4e07-9d20-7985964786f9" >
<name><text>Description</text></name>
</ClassA>
How can i make this? I dont want to create a new class for the Name-Property..
Thanks :)
One possibility would be to turn Name into a complex class; for example:
[XmlElement("name")]
public MyText Name
{
get { return GetProperty(() => Name); }
set { SetProperty(() => Name, value); }
}
Then you could define MyText as:
public class MyText
{
public string Text {get;set;}
}
You can use XMLReader:
ClassA a = new ClassA();
using (XmlTextReader reader = new XmlTextReader("books.xml"))
{
while (reader.Read())
{
switch (reader.Name)
{
case "text":
//do something with this node, like:
a.Name = reader.Value;
break;
}
}
}
Alternatively, if you want to stick to object deserialization, you should get the exact matching-to-xml class, so refer to this:
XML2CSharp
You could implement custom serialisation, the ClassA would need to implement ISerializable. Alternatively implement the custom serialization using ISerializationSurrogate and its related interfaces.
However that is not something I'd do. Its likely going to be much more complicated than having a wrapper class for Name.
You can customize serialization process. For example ClassA could be decorated with attribute:
public class ClassA : BaseClass, IXmlSerializable
{
[XmlElement("name/text")]
public string Name
{
get { return GetProperty(() => Name); }
set { SetProperty(() => Name, value); }
}
}
Then the BaseClass class should implement IXmlSerializable like this:
public class BaseClass : IXmlSerializable
{
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
foreach (PropertyInfo propertyInfo in this.GetType().GetProperties())
{
XmlElementAttribute elementAttribute = propertyInfo.GetCustomAttribute<XmlElementAttribute>(true);
if (elementAttribute != null)
{
string[] elementNames = elementAttribute.ElementName.Split('/', '\\');
foreach (string elementName in elementNames)
{
reader.ReadStartElement(elementName);
}
propertyInfo.SetValue(this, reader.ReadContentAsString());
foreach (string elementName in elementNames)
{
reader.ReadEndElement();
}
}
}
}
public void WriteXml(XmlWriter writer)
{
foreach (PropertyInfo propertyInfo in this.GetType().GetProperties())
{
XmlElementAttribute elementAttribute = propertyInfo.GetCustomAttribute<XmlElementAttribute>(true);
if (elementAttribute != null)
{
string[] elementNames = elementAttribute.ElementName.Split('/', '\\');
foreach (string elementName in elementNames)
{
writer.WriteStartElement(elementName);
}
writer.WriteString(propertyInfo.GetValue(this).ToString());
foreach (string elementName in elementNames)
{
writer.WriteEndElement();
}
}
}
}
protected string GetProperty(Func<string> f)
{
return "text-value";
}
protected void SetProperty<T>(Func<T> f, T value)
{
}
}
The result of serialization will be:
<ClassA>
<name>
<text>text-value</text>
</name>
</ClassA>

XMLserialize object of list and string

So i need to xml serialize a generic list of objects where the object consists of another list and a string.
This is what i get now
<?xml version="1.0" encoding="UTF-8"?>
-<ArrayOfRecept xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
-<Recept>
<Namn>Nomnom</Namn>
</Recept>
-<Recept>
<Namn>Ännu mer nomnom</Namn>
</Recept>
</ArrayOfRecept>
Here only the string value is serialized and not my List
This is my object that i want to serialize
public class Recept
{
private ListHanterare<string> ingredienser;
private string namn;
public Recept()
{
ingredienser = new ListHanterare<string>();
}
public ListHanterare<string> Ingredienser
{
get { return ingredienser; }
}
public string Namn
{
get { return namn; }
set { namn = value; }
}
}
So I will have a list of Recept that i want to xml serialize and I want the xml to show both "Namn" and the "Ingredienser"-list.
This is my serializer
class XMLSerial
{
public static bool Serialize<T>(T obj, string filePath)
{
bool bok = true;
XmlSerializer serializer = new XmlSerializer(typeof(T));
TextWriter writer = new StreamWriter(filePath);
try
{
serializer.Serialize(writer, obj);
}
catch
{
bok = false;
}
finally
{
if (writer != null)
writer.Close();
}
return bok;
}
}
This is inside the ListHanterare class where I pass the object to the serializer
public bool XMLSerialize(string filePath){
return XMLSerial.Serialize<List<T>>(lista, filePath);
EDIT:
So by adding a setter to ingredienser I now get this
-<Recept>
<Ingredienser/>
<Namn>rec</Namn>
</Recept>
But ingredienser is still empty
My ListHanterare class is a basic generic List class
public class ListHanterare<T> : IListHanterare<T>
{
private List<T> lista; // This is the list
private int count;
public ListHanterare()
{
lista = new List<T>();
}
So i need to serialize a ListHanterare list of Recept objects where the Recept object consists of a string and another ListHanterare list of strings, the string is serialized correctly but not the list of strings.
In order for XmlSerializer to serialize or deserialize the property Ingredienser of your Recept class, it must have a public setter as well as a public getter:
public ListHanterare<string> Ingredienser
{
get { return ingredienser; }
set { ingredienser = value; }
}
If you don't do this, XmlSerializer will ignore the property.
You might still have problems with the class ListHanterare, but it's not shown in your question.
DataContractSerializer does not have this requirement. However, to serialize and deserialize your classes with DataContractSerializer and include private fields or properties, you would need to fully annotate them with data contract attributes. Private field and properties marked with [DataMember] get serialized despite being private. Data contract serialization is opt-in, however, so to use this feature your classes would need to be fully attributed.
Update
You don't show the complete code of your ListHanterare<string> class. In order for XmlSerializer to serialize and deserialize it successfully, it must either:
Make all its contents available in publicly settable and gettable properties, or
Implement ICollection<T> (or even IList<T>) as described here: XmlSerializer Class.
For method #1, you could modify your class as follows:
public class ListHanterare<T>
{
private List<T> lista; // This is the list
private int count;
public ListHanterare()
{
lista = new List<T>();
}
[XmlElement("Item")]
public T[] SerializableList
{
get
{
return (lista == null ? new T [0] : lista.ToArray());
}
set
{
if (lista == null)
lista = new List<T>();
lista.Clear();
lista.AddRange(value ?? Enumerable.Empty<T>());
count = lista.Count;
}
}
}
You will now see the items serialized your XML.

How to use .NET XmlSerializer to serialzie null and empty value with special format

I want to use the .NET XmlSerializer class to serialize a class object to an XML string. both null string and empty string should be serialized.
if a property value is null then XML format like
<property />
if a property value is an empty string, then the serialized XML format is
<property></property>
When you need custom serialization, you'll need to implement your own implementation of the IXmlSerialization interface. This provides custom formatting for XML serialization and deserialization. For example :
public class Person : IXmlSerializable
{
// Private state
private string personName;
// Constructors
public Person (string name)
{
personName = name;
}
public Person ()
{
personName = null;
}
// Xml Serialization Infrastructure
public void WriteXml (XmlWriter writer)
{
writer.WriteString(personName);
}
public void ReadXml (XmlReader reader)
{
personName = reader.ReadString();
}
public XmlSchema GetSchema()
{
return(null);
}
// Print
public override string ToString()
{
return(personName);
}
}
You can use properties of class without initialize. It will like:
class XmlData{
public string Person;
public XmlData(){
Person=null;
}
}
class Program{
static void Main(){
var xmlTaplate=new XmlData();//in this step property Person is null
var serializer = new XmlSerializer(typeof(xmlTemplate));
using (TextWriter writer = new StreamWriter(#"person.xml"))
{
serializer.Serialize(writer, details);
}
}
}

C# XML serialization - Change array item name

I need to serialize objects with List property to XML to get XML code like this (I know it's not valid XML but my 3rd party application need this format):
<Filters>
<Criteria_0 Parameter="STATUS"
Operator="EQUAL"
Value="STARTED" />
<Criteria_1 Parameter="STATUS"
Operator="EQUAL"
Value="COMPLETED" />
</Filters>
I wrote code like this:
public class JobStatusListTask
{
public JobListSettings ListSettings;
public List<JobFilterCriteria> Filters;
public JobStatusListTask()
{
Filters = new List<JobFilterCriteria>();
Filters.Add(new JobFilterCriteria("STATUS", CriteriaOperator.Equal, "ERROR"));
}
public JobStatusListTask(JobListSettings settings) : this()
{
ListSettings = settings;
}
}
public class JobFilterCriteria : IXmlSerializable
{
public static int Count = 0;
public string Parameter;
public CriteriaOperator Operator;
public string Value;
private JobFilterCriteria()
{
Parameter = string.Empty;
Value = string.Empty;
}
public JobFilterCriteria(string parameter, CriteriaOperator criteriaOperator, string value)
{
Parameter = parameter;
Operator = criteriaOperator;
Value = value;
}
XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
void IXmlSerializable.ReadXml(XmlReader reader)
{
throw new NotImplementedException();
}
void IXmlSerializable.WriteXml(XmlWriter writer)
{
writer.WriteStartElement(string.Format("Criteria_{0}", Count++));
writer.WriteAttributeString("Parameter", Parameter);
writer.WriteAttributeString("Operator", Operator.ToString());
writer.WriteAttributeString("Value", Value);
}
}
It works almost perfect. Serializer return XML code with unnecessary JobFilterCriteria element.
<?xml version="1.0" encoding="utf-8"?>
<Filters>
<JobFilterCriteria>
<Criteria_0
Parameter="STATUS" Operator="Equal" Value="ERROR" />
</JobFilterCriteria>
<JobFilterCriteria>
<Criteria_1
Parameter="STATUS" Operator="Equal" Value="STARTED" />
</JobFilterCriteria>
</Filters>
What I need to change to remove JobFilterCriteria from XML code?
I think, this code block will solve your problem, you need to serialize JobStatusListTask class.
public class JobStatusListTask : IXmlSerializable
{
public JobListSettings ListSettings;
public List<JobFilterCriteria> Filters;
public JobStatusListTask()
{
Filters = new List<JobFilterCriteria>();
Filters.Add(new JobFilterCriteria("STATUS", CriteriaOperator.Equal, "ERROR"));
}
public JobStatusListTask(JobListSettings settings) : this()
{
ListSettings = settings;
}
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("Filters");
foreach(var item in Filters)
{
writer.WriteStartElement("Criteria", string.Format("Criteria_{0}", Count++));
writer.WriteAttributeString("Parameter", Parameter);
writer.WriteAttributeString("Operator", Operator.ToString());
writer.WriteAttributeString("Value", Value);
}
writer.WriteEndElement();
}
}
I can't comment on answers, but I don't see how the above compiles as is, and it seems the namespace is being set accidentally, I'm sure you worked it out, but for posterity, a few changes that I believe are needed to make it work as intended:
public class JobStatusListTask : IXmlSerializable
{
public JobListSettings ListSettings;
public List<JobFilterCriteria> Filters;
public JobStatusListTask()
{
Filters = new List<JobFilterCriteria>();
Filters.Add(new JobFilterCriteria("STATUS", CriteriaOperator.Equal, "ERROR"));
}
public JobStatusListTask(JobListSettings settings) : this()
{
ListSettings = settings;
}
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("Filters");
for(int i = 0; i < Filters.Count; i++)
{
writer.WriteStartElement(string.Format("Criteria_{0}", i));
writer.WriteAttributeString("Parameter", Parameter);
writer.WriteAttributeString("Operator", Operator.ToString());
writer.WriteAttributeString("Value", Value);
}
writer.WriteEndElement();
}
}

how to read in a list of custom configuration objects

I want to implement Craig Andera's custom XML configuration handler in a slightly different scenario. What I want to be able to do is to read in a list of arbitrary length of custom objects defined as:
public class TextFileInfo
{
public string Name { get; set; }
public string TextFilePath { get; set; }
public string XmlFilePath { get; set; }
}
I managed to replicate Craig's solution for one custom object but what if I want several?
Craig's deserialization code is:
public class XmlSerializerSectionHandler : IConfigurationSectionHandler
{
public object Create(object parent, object configContext, XmlNode section)
{
XPathNavigator nav = section.CreateNavigator();
string typename = (string)nav.Evaluate("string(#type)");
Type t = Type.GetType(typename);
XmlSerializer ser = new XmlSerializer(t);
return ser.Deserialize(new XmlNodeReader(section));
}
}
I think I could do this if I could get
Type t = Type.GetType("System.Collections.Generic.List<TextFileInfo>")
to work but it throws
Could not load type 'System.Collections.Generic.List<Test1.TextFileInfo>' from assembly 'Test1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
I don't think this would work in that scenario. Craig's solution works well for simple object graphs, but collections are a little trickier. Lists are serialised as arrays, so putting your example in, a serialised List is something like:
<ArrayOfTextFileInfo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlsns:xsd="http://www.w3.org/2001/XMLSchema>
<TextFileInfo>
<Name>My Text File</Name>
<TextFilePath>C:\MyTextFile.txt</TextFilePath>
<XmlFilePath>C:\MyXmlFile.xml</XmlFilePath>
</TextFileInfo>
</ArrayOfTextFileInfo>
Now, I'm guessing you could probably put that in a config, as long as the config section is named as "ArrayOfTextFileInfo". Not exactly that friendly. I think what you should probably do is use the standard configuration classes to build this:
public class TextFileConfigurationElement : ConfigurationElement
{
[ConfigurationProperty("name", IsRequired = true, IsKey = true)]
public string Name {
get { return (string)this["name"]; }
set { this["name"] = value; }
}
[ConfigurationProperty("textFilePath")]
public string TextFilePath {
get { return (string)this["textFilePath"]; }
set { this["textFilePath"] = value; }
}
[ConfigurationProperty("xmlFilePath")]
public string XmlFilePath {
get { return (string)this["xmlFilePath"]; }
set { this["xmlFilePath"] = value; }
}
}
[ConfigurationCollection(typeof(TextFileConfigurationElement))]
public class TextFileConfigurationElementCollection : ConfigurationElementCollection
{
protected override void CreateNewElement() {
return new TextFileConfigurationElement();
}
protected override object GetElementKey(ConfigurationElement element) {
return ((TextFileConfigurationElement)element).Name;
}
}
public class TextFilesConfigurationSection : ConfigurationSection
{
[ConfigurationProperty("files")]
public TextFileConfigurationElementCollection Files {
get { return (TextFileConfigurationElementCollection)this["files"]; }
set { this["files"] = value; }
}
public static TextFilesConfigurationSection GetInstance() {
return ConfigurationManager.GetSection("textFiles") as TextFilesConfigurationSection;
}
}
Once you've registered the config section:
<configSections>
<add name="textFiles" type="...{type here}..." />
</configSections>
You can add in the configs:
<textFiles>
<files>
<add name="File01" textFilePath="C:\File01.txt" xmlTextFile="C:\File01.xml" />
</files>
</textFiles>
Using that in code:
public List<TextFileInfo> GetFiles() {
var list = new List<TextFileInfo>();
var config = TextFileConfigurationSection.GetInstance();
if (config == null)
return list;
foreach (TextFileConfigurationElement fileConfig in config.Files) {
list.Add(new TextFileInfo
{
Name = fileConfig.Name,
TextFilePath = fileConfig.TextFilePath,
XmlFilePath = fileConfig.XmlFilePath
});
}
return list;
}
Also, this:
Type t = Type.GetType("System.Collections.Generic.List<TextFileInfo>")
Won't work for a couple of reasons, you haven't fully qualified the TextFileInfo type (needs a namespace), and your definition of a generic type is wrong (I can see why you haven't specified it that way), it should look like:
Type t = Type.GetType("System.Collections.Generic.List`1[MyNamespace.TextFileInfo]");
Hope that helps!

Categories