How to save the Interface collections in xml in wpf - c#

I have a Model with interface collection. I want to save the collection in xml file at runtime in temp location. Without interface collection the Model is saved correctly in xml file. But the interface collection in not saved in xml file. Please anyone help me to achieve this. My Model class structure is mentioned below,
MainWindowModel
public class MainWindowModel
{
private string header;
public string Header
{
get { return header; }
set { header = value; }
}
private bool isEditing = false;
public bool IsEditing
{
get { return isEditing; }
set { isEditing = value; }
}
public ObservableCollection<Details> DetailsCollection { get; set; }
}
Details
public class Details
{
public string Key { get; set; }
public ObservableCollection<IValue> Values { get; set; }
}
IValue
public interface IValue
{
int Id { get; set; }
string Name { get; set; }
}
FileReaderWriter
public class FileReaderWriter<T>
{
public string FileLocation;
public T Fetch()
{
if (File.Exists(FileLocation))
{
XmlSerializer deserializer = new XmlSerializer(typeof(T));
TextReader reader = new StreamReader(FileLocation);
object obj = deserializer.Deserialize(reader);
T XmlData = (T)obj;
reader.Close();
return XmlData;
}
return default(T);
}
public virtual string GetFileLocation()
{
return FileLocation;
}
public void Save(T model)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
var directory = Path.GetDirectoryName(FileLocation);
if (!string.IsNullOrEmpty(directory))
{
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
using (var writer = XmlWriter.Create(FileLocation))
{
serializer.Serialize(writer, model);
}
}
}
}
ReaderWriterClass
public class DetailsViewReaderWriter : FileReaderWriter<ObservableCollection<MainWindowModel>>
{
public DetailsViewReaderWriter()
{
FileLocation = ConfigurationManager.AppSettings["RecentFileLocation"];
}
public ObservableCollection<MainWindowModel> FetchFile()
{
var recentFile = Fetch();
return recentFile;
}
public override string GetFileLocation()
{
return FileLocation;
}
public void SaveFile(ObservableCollection<MainWindowModel> fileModel)
{
Save(fileModel);
}
}
App.config
<appSettings>
<add key="RecentFileLocation" value="D:\MyProject\RecentDetails.xml"/>
</appSettings>

The XmlSerializer cannot serialize Interface types. What you can do is to implement the IXmlSerializable interface. Please find below a rough! example. It is not tested. You should read carefully on how to implement the IXmlSerializable interface correctly. But to give you an idea and help to get started:
Adjust your IValue interface:
public interface IValue: IXmlSerializable
{
int Id { get; set; }
string Name { get; set; }
}
Implement the interface:
public class Value : IValue
{
public int Id { get; set; }
public string Name { get; set; }
public XmlSchema GetSchema() => null;
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
Name = reader.GetAttribute("Name");
Id = int.Parse(reader.GetAttribute("Id"));
}
public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("Id", Id.ToString());
writer.WriteAttributeString("Name", Name);
}
}

Related

Serialize in XmlElement list of XmlAttribute's

I want to serialize a list of XmlAttribute's in a XmlElement.
Some general info about the program:
It's a program where you can add product's with some attributes like the name, a description, and some properties (height, width, brand, condition), but the user can choose how many he want to add (not a fixed number).
Here is the class diagramm of the program:
Here is the code of the ManagerRoot class:
[Serializable]
public class ManagerRoot
{
[XmlElement("ProductRoot")]
public ProductRoot ProductRoot = new ProductRoot();
}
Here is the code of the ProductRoot Class:
[Serializable]
public class ProductRoot
{
[XmlElement("Product")]
public List<Product> ProductList { get; set; }
private void addProduct()
{
//?
}
}
Here is the code of the Product class:
[Serializable]
public class Product
{
[XmlIgnore]
private string _header;
[XmlAttribute("Header")]
public string Header
{
get { return this._header; }
set { this._header = value; }
}
[XmlIgnore]
private string _description;
[XmlAttribute("Description")]
public string Description
{
get { return this._description; }
set { this._description = value; }
}
[XmlIgnore]
private string _link;
[XmlAttribute("Link")]
public string Link
{
get { return this._link; }
set { this._link = value; }
}
[XmlIgnore]
private List<string> _properties;
[XmlAttribute("Properties")]
public List<string> Properties
{
get
{
}
set
{
}
}
}
Now I don't know what i have to do with the getter and setter of the list inside the class. And how can I add a new product with a name, description, link & a filled properties list?
For collections you need to annotate the property with XmlArray and XmlArrayItem to define which is the contained item. Please follow the same ProductList pattern for all collection based properties.
namespace ConsoleApplication1
{
[Serializable]
public class ProductRoot
{
[XmlArray]
[XmlArrayItem(ElementName = "Product", Type = typeof(Product))]
public List<Product> ProductList { get; set; }
}
[Serializable]
public class Product
{
[XmlIgnore]
private string _header;
[XmlAttribute("Header")]
public string Header {
get { return this._header; }
set { this._header = value; }
}
[XmlIgnore]
private string _description;
[XmlAttribute("Description")]
public string Description {
get { return this._description; }
set { this._description = value; }
}
[XmlIgnore]
private string _link;
[XmlAttribute("Link")]
public string Link {
get { return this._link; }
set { this._link = value; }
}
[XmlIgnore]
private List<string> _properties;
[XmlAttribute("Properties")]
public List<string> Properties { get; set; }
}
static class Program
{
static void Main() {
var productRoot = new ProductRoot();
var products = new List<Product>();
products.Add(new Product() { Description = "A", Properties = new List<string> { "PropA" } });
products.Add(new Product() { Description = "B", Properties = new List<string> { "PropB" } });
productRoot.ProductList = products;
Test(productRoot);
}
static void Test<T>(T obj) {
using (MemoryStream stream = new MemoryStream()) {
XmlSerializer s = new XmlSerializer(typeof(T));
using (var stringWriter = new StringWriter()) {
using (var writer = XmlWriter.Create(stringWriter)) {
s.Serialize(stringWriter, obj);
}
Console.WriteLine(stringWriter.ToString());
}
}
}
}

XML Deserialization Rules

I need to deserialize xml file and its structured this way:
<NPCs>
<LabAssistant1>
<Questions>
<Question>
<Type>CheckBox</Type>
<Points>10</Points>
<Text>Q1</Text>
<Answers>
<Answer>
<Correct>False</Correct>
<Text>A1</Text>
</Answer>
<Answer>
<Correct>True</Correct>
<Text>A2</Text>
</Answer>
<Answer>
<Correct>False</Correct>
<Text>A3</Text>
</Answer>
</Answers>
</Question>
</Questions>
</LabAssistant1>
<LabAssistant2>
<Questions>
...
</Questions>
</LabAssistant2>
</NPCs>
So as you can see am having root node NPCs and my goal is to read questions separately by LabAssistant1 name or any tag name in NPCs.
String questionsPath = path+"/questions.xml";
XmlReader reader=XmlReader.Create(new StreamReader(questionsPath));
XmlRootAttribute xmlRoot = new XmlRootAttribute();
xmlRoot.ElementName = npc;
reader.ReadToDescendant(npc);
XmlSerializer se = new XmlSerializer(typeof(Question[]),xmlRoot);
Question[] qs=se.Deserialize(reader) as Question[];
Console.WriteLine(qs.Length.ToString()); // Always 0
Above code should output 2 objects of Question as array, but it doesn't
Here are the classes Question and Answer, anything is wrong with my attached attributes?
public class Question
{
[XmlElement(ElementName="Text")]
public String Text { get; set; }
[XmlArray(ElementName = "Answers")]
public Answer[] Answers { get; set; }
[XmlElement(ElementName = "Type")]
public QuestionType Type { get; set; }
[XmlElement(ElementName = "Points")]
public int Points { get; set; }
public Question()
{
}
public Question(String text, Answer[] answers, QuestionType type,int points)
{
this.Text = text;
this.Answers = answers;
this.Type = type;
this.Points = points;
}
}
public class Answer
{
[XmlElement(ElementName="Text")]
public String Text { get; set; }
[XmlElement(ElementName = "Correct")]
public bool Correct { get; set; }
public Answer()
{
}
public Answer(String text, bool correct)
{
this.Text = text;
this.Correct = correct;
}
}
You could use the UnknownElement event of XmlSerializer to load all the lab assistants into memory, like so:
public class LabAssistant
{
static XmlSerializer listSerializer;
static LabAssistant()
{
// This must be cached to prevent memory & resource leaks.
// See http://msdn.microsoft.com/en-us/library/System.Xml.Serialization.XmlSerializer%28v=vs.110%29.aspx
listSerializer = new XmlSerializer(typeof(List<Question>), new XmlRootAttribute("Questions"));
}
public List<Question> Questions { get; set; }
public static bool TryDeserializeFromXml(XmlElement element, out string name, out LabAssistant assistant)
{
name = element.Name;
var child = element.ChildNodes.OfType<XmlElement>().Where(el => el.Name == "Questions").FirstOrDefault();
if (child != null)
{
var list = child.OuterXml.LoadFromXML<List<Question>>(listSerializer);
if (list != null)
{
assistant = new LabAssistant() { Questions = list };
return true;
}
}
assistant = null;
return false;
}
}
public class NPCs
{
public NPCs()
{
this.LabAssistants = new Dictionary<string, LabAssistant>();
}
public static XmlSerializer CreateXmlSerializer()
{
// No need to cache this.
var serializer = new XmlSerializer(typeof(NPCs));
serializer.UnknownElement += new XmlElementEventHandler(NPCs.XmlSerializer_LoadLabAssistants);
return serializer;
}
[XmlIgnore]
public Dictionary<string, LabAssistant> LabAssistants { get; set; }
public static void XmlSerializer_LoadLabAssistants(object sender, XmlElementEventArgs e)
{
var obj = e.ObjectBeingDeserialized;
var element = e.Element;
if (obj is NPCs)
{
var npcs = (NPCs)obj;
string name;
LabAssistant assistant;
if (LabAssistant.TryDeserializeFromXml(element, out name, out assistant))
npcs.LabAssistants[name] = assistant;
}
}
}
Using the following helper methods:
public static class XmlSerializationHelper
{
public static T LoadFromXML<T>(this string xmlString)
{
return xmlString.LoadFromXML<T>(new XmlSerializer(typeof(T)));
}
public static T LoadFromXML<T>(this string xmlString, XmlSerializer serial)
{
T returnValue = default(T);
using (StringReader reader = new StringReader(xmlString))
{
object result = serial.Deserialize(reader);
if (result is T)
{
returnValue = (T)result;
}
}
return returnValue;
}
}
Having done this, you now have a dictionary of lab assistants by name.
While this code will deserialize your data correctly, it won't reserialize it. Custom code to serialize the dictionary would be required.
One final note - XmlSerializer will choke on the XML you provided because it requires that Boolean values be in lowercase. Thus the following will throw an exception:
<Correct>False</Correct>
If you did not mistype the XML and it really contains Booleans in this format, you will need to manually handle these fields.
I needed to create QuestionCollection class to hold the array of questions (having typeof(Question[]) throws <TagName xmlns="> was not expected, probably because the deserializer is not smart enough).
What i do next is first reading to tag LabAssistant or any tag name, next reading to its child Questions tag and after that i deserialize the questions into QuestionCollection, so with ReadToDescendant I can access any child elements of the NPCs
String questionsPath = Application.dataPath + "/Resources/questions.xml";
XmlReader reader=XmlReader.Create(new StreamReader(questionsPath));
reader.ReadToDescendant("LabAssistant");
reader.ReadToDescendant("Questions");
XmlSerializer se = new XmlSerializer(typeof(QuestionCollection));
QuestionCollection qc=(QuestionCollection)se.Deserialize(reader);
QuestionCollection class:
[XmlType("Questions")]
public class QuestionCollection
{
[XmlElement("Question")]
public Question[] Questions { get; set; }
public QuestionCollection() { }
}
Question class
[XmlType("Question")]
public class Question
{
[XmlElement("Text")]
public String Text { get; set; }
[XmlArray("Answers")]
public Answer[] Answers { get; set; }
[XmlElement("Type")]
public QuestionType Type { get; set; }
[XmlElement("Points")]
public int Points { get; set; }
public Question() { }
}
Answer class:
[XmlType("Answer")]
public class Answer
{
[XmlElement("Text")]
public String Text { get; set; }
[XmlElement("Correct")]
public bool Correct { get; set; }
public Answer() { }
}

Assigning a default value to a property

I have an xml like so:
<Settings>
<User default="Programmer"></User>
<Level default="2"></Level>
<Settings>
This is deserialized to an object of type UserSettings:
[Serializable]
[XmlRoot("Settings")]
public class UserSettings
{
[XmlElement("User")]
public string User { get; set; }
[XmlElement("Level")]
public string Level { get; set; }
}
The UserSettings object gives whatever the values are there for the tags at runtime.
I want the class to return the default attribute value when either the tag is empty or the tag is absent in the incoming xml.
So if there is an object objUserSettings of type UserSettings then
objUserSettings.User
should give "Programmer", or whatever is in default attribute value in the xml if the tag User is empty.
Regards.
Adding another answer because I had some fun with this question. Take it or leave it, but this is probably how I would attack this feature.
Here's an answer that is more complicated, but it gives you type safety using generics and most of the heavy lifting is done in one base class (no need to copy/paste the same code over and over).
Added a property to UserSettings to show an example of another type...
[Serializable]
[XmlRoot("Settings")]
public class UserSettings
{
public UserSettings()
{
User = new DefaultableStringValue();
Level = new DefaultableIntegerValue();
IsFullscreen = new DefaultableBooleanValue();
}
[XmlElement("User")]
public DefaultableStringValue User { get; set; }
[XmlElement("Level")]
public DefaultableIntegerValue Level { get; set; }
[XmlElement("IsFullscreen")]
public DefaultableBooleanValue IsFullscreen { get; set; }
}
Simple implementations of typed DefaultableValues...
[Serializable]
public class DefaultableStringValue : DefaultableValue<string>
{
public DefaultableStringValue() : base(s => s) { }
}
[Serializable]
public class DefaultableIntegerValue : DefaultableValue<int>
{
public DefaultableIntegerValue() : base(int.Parse) { }
}
[Serializable]
public class DefaultableBooleanValue : DefaultableValue<bool>
{
public DefaultableBooleanValue() : base(bool.Parse) { }
}
Base class that does all of the heavy lifting of parsing and caching parsed values...
[Serializable]
public abstract class DefaultableValue<T>
{
protected Func<string, T> _parsingFunc;
private string _valueText;
private T _cachedValue;
private bool _isValueCached;
private string _defaultText;
private T _cachedDefault;
private bool _isDefaultCached;
protected DefaultableValue(Func<string, T> parsingFunc)
{
_parsingFunc = parsingFunc;
_isValueCached = false;
_isDefaultCached = false;
}
[XmlAttribute("default")]
public string DefaultText
{
get { return _defaultText; }
set
{
_defaultText = value;
_isDefaultCached = false;
}
}
[XmlText]
public string ValueText
{
get { return _valueText; }
set
{
_valueText = value;
_isValueCached = false;
}
}
[XmlIgnore]
public T Default
{
get
{
if (_isDefaultCached)
return _cachedDefault;
if (HasDefault)
return ParseAndCacheValue(DefaultText, out _cachedDefault, out _isDefaultCached);
return default(T);
}
set
{
DefaultText = value.ToString();
_cachedDefault = value;
_isDefaultCached = true;
}
}
[XmlIgnore]
public T Value
{
get
{
if (_isValueCached)
return _cachedValue;
if (HasValue)
return ParseAndCacheValue(ValueText, out _cachedValue, out _isValueCached);
return Default;
}
set
{
ValueText = value.ToString();
_cachedValue = value;
_isValueCached = true;
}
}
[XmlIgnore]
public bool HasDefault { get { return !string.IsNullOrEmpty(_defaultText); } }
[XmlIgnore]
public bool HasValue { get { return !string.IsNullOrEmpty(_valueText); } }
private T ParseAndCacheValue(string text, out T cache, out bool isCached)
{
cache = _parsingFunc(text);
isCached = true;
return cache;
}
}
And a sample program to demonstrate usage...
public class Program
{
private static void Main(string[] args)
{
UserSettings userSettings = new UserSettings();
userSettings.User.Default = "Programmer";
userSettings.Level.Default = 2;
userSettings.Level.Value = 99;
XmlSerializer xmlSerializer = new XmlSerializer(typeof(UserSettings));
string serializedUserSettings;
using (StringWriter stringWriter = new StringWriter())
{
xmlSerializer.Serialize(stringWriter, userSettings);
serializedUserSettings = stringWriter.GetStringBuilder().ToString();
}
UserSettings deserializedUserSettings;
using (StringReader stringReader = new StringReader(serializedUserSettings))
{
deserializedUserSettings = (UserSettings)xmlSerializer.Deserialize(stringReader);
}
Console.Out.WriteLine("User: HasDefault={0}, Default={1}, HasValue={2}, Value={3}",
deserializedUserSettings.User.HasDefault ? "Yes" : "No",
deserializedUserSettings.User.Default,
deserializedUserSettings.User.HasValue ? "Yes" : "No",
deserializedUserSettings.User.Value);
Console.Out.WriteLine("Level: HasDefault={0}, Default={1}, HasValue={2}, Value={3}",
deserializedUserSettings.Level.HasDefault ? "Yes" : "No",
deserializedUserSettings.Level.Default,
deserializedUserSettings.Level.HasValue ? "Yes" : "No",
deserializedUserSettings.Level.Value);
Console.Out.WriteLine("IsFullscreen: HasDefault={0}, Default={1}, HasValue={2}, Value={3}",
deserializedUserSettings.IsFullscreen.HasDefault ? "Yes" : "No",
deserializedUserSettings.IsFullscreen.Default,
deserializedUserSettings.IsFullscreen.HasValue ? "Yes" : "No",
deserializedUserSettings.IsFullscreen.Value);
Console.ReadLine();
}
}
Try this
using System.ComponentModel;
[Serializable]
[XmlRoot("Settings")]
public class UserSettings
{
[DefaultValue("Yogesh")]
[XmlElement("User")]
public string User { get; set; }
[DefaultValue("1st")]
[XmlElement("Level")]
public string Level { get; set; }
}
For more info see this.
You can use Default Value attribute for the property.
In you case it will be,
[Serializable]
[XmlRoot("Settings")]
public class UserSettings
{
[XmlElement("User")]
[DefaultValue("Programmer")]
public string User { get; set; }
[XmlElement("Level")]
[DefaultValue(2)]
public string Level { get; set; }
}
I don't believe there is a way to tell string to use that default xml attribute. You will have to deserialize each of those object into a structure that has the default value as a property which is an xml attribute.
Here's an example...
[Serializable]
[XmlRoot("Settings")]
public class UserSettings
{
[XmlElement("User")]
public DefaultableValue User { get; set; }
[XmlElement("Level")]
public DefaultableValue Level { get; set; }
}
[Serializable]
public class DefaultableValue
{
[XmlAttribute("default")]
public string Default { get; set; }
[XmlText]
public string Value { get; set; }
}
And sample program to demonstrate usage...
public class Program
{
private static void Main(string[] args)
{
UserSettings userSettings = new UserSettings();
userSettings.User = new DefaultableValue()
{
Default = "Programmer",
Value = "Tyler"
};
userSettings.Level = new DefaultableValue()
{
Default = "2",
Value = "99"
};
XmlSerializer xmlSerializer = new XmlSerializer(typeof(UserSettings));
string serializedUserSettings;
using (StringWriter stringWriter = new StringWriter())
{
xmlSerializer.Serialize(stringWriter, userSettings);
serializedUserSettings = stringWriter.GetStringBuilder().ToString();
}
UserSettings deserializedUserSettings;
using (StringReader stringReader = new StringReader(serializedUserSettings))
{
deserializedUserSettings = (UserSettings)xmlSerializer.Deserialize(stringReader);
}
Console.Out.WriteLine("User: Default={0}, Actual={1}",
deserializedUserSettings.User.Default,
deserializedUserSettings.User.Value);
Console.Out.WriteLine("Level: Default={0}, Actual={1}",
deserializedUserSettings.Level.Default,
deserializedUserSettings.Level.Value);
}
}
(Note that I have the default values in code, but they very well could have come from the xml file)

XML Serialize List of generic objects derived from an interface,,,

So I'm trying to XML serialize a List<IObject> derrived from an interface, but the IObjects are generic types... best resort to code:
public interface IOSCMethod
{
string Name { get; }
object Value { get; set; }
Type Type { get; }
}
public class OSCMethod<T> : IOSCMethod
{
public string Name { get; set; }
public T Value { get; set; }
public Type Type { get { return _type; } }
protected string _name;
protected Type _type;
public OSCMethod() { }
// Explicit implementation of IFormField.Value
object IOSCMethod.Value
{
get { return this.Value; }
set { this.Value = (T)value; }
}
}
And I have a List of IOSCMethods:
List<IOSCMethod>
of which I add objects to in the following way:
List<IOSCMethod> methodList = new List<IOSCMethod>();
methodList.Add(new OSCMethod<float>() { Name = "/1/button1", Value = 0 });
methodList.Add(new OSCMethod<float[]>() { Name = "/1/array1", Value = new float[10] });
And it's this methodList which is what I'm trying to serialize. But everytime I try, either I get a "Can't serialize an interface" but when I make it (either the IOSCMethod or the OSCMethod<T> class) implement IXmlSerializable I get the problem of "can't serialize an object with a parameterless constructor. but obviously I can't because it's an interface! lame pants.
Any thoughts?
Is this what you want:
[TestFixture]
public class SerializeOscTest
{
[Test]
public void SerializeEmpTest()
{
var oscMethods = new List<OscMethod>
{
new OscMethod<float> {Value = 0f},
new OscMethod<float[]> {Value = new float[] {10,0}}
};
string xmlString = oscMethods.GetXmlString();
}
}
public class OscMethod<T> : OscMethod
{
public T Value { get; set; }
}
[XmlInclude(typeof(OscMethod<float>)),XmlInclude(typeof(OscMethod<float[]>))]
public abstract class OscMethod
{
}
public static class Extenstion
{
public static string GetXmlString<T>(this T objectToSerialize)
{
XmlSerializer xmlSerializer = new XmlSerializer(objectToSerialize.GetType());
StringBuilder stringBuilder = new StringBuilder();
string xml;
using (var xmlTextWriter = new XmlTextWriter(new StringWriter(stringBuilder)))
{
xmlSerializer.Serialize(xmlTextWriter, objectToSerialize);
xml = stringBuilder.ToString();
}
return xml;
}
}

XML serialization in .net

I'm trying to serialize an object to meet another systems requirements.
It need to look like this:
<custom-attribute name="Colour" dt:dt="string">blue</custom-attribute>
but instead is looking like this:
<custom-attribute>blue</custom-attribute>
So far I have this:
[XmlElement("custom-attribute")]
public String Colour{ get; set; }
I'm not really sure what metadata I need to achieve this.
You could implement IXmlSerializable:
public class Root
{
[XmlElement("custom-attribute")]
public Colour Colour { get; set; }
}
public class Colour : IXmlSerializable
{
[XmlText]
public string Value { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
throw new NotImplementedException();
}
public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("dt:dt", "", "string");
writer.WriteAttributeString("name", "Colour");
writer.WriteString(Value);
}
}
class Program
{
static void Main()
{
var serializer = new XmlSerializer(typeof(Root));
var root = new Root
{
Colour = new Colour
{
Value = "blue"
}
};
serializer.Serialize(Console.Out, root);
}
}

Categories

Resources