custom Xml Serializer to make value as an xml element - c#

Classes:
public class Employee
{
public int Id { get; set; }
public PhoneNumber[] Numbers { get; set; }
}
public class PhoneNumber
{
public string Type { get; set; }
public string Number { get; set; }
}
Code to serialize:
var xmlSerializer = new XmlSerializer(typeof(Employee));
var xwSettings = new XmlWriterSettings {Indent = true, OmitXmlDeclaration = true};
string serializedResult;
using (var stream = new StringWriter())
using (var writer = XmlWriter.Create(stream, xwSettings))
{
xmlSerializer.Serialize(writer, emp);
serializedResult = stream.ToString();
}
Current Result:
<Employee>
<Id>1</Id>
<Numbers>
<PhoneNumber>
<Type>Home</Type>
<Number>1231231231</Number>
</PhoneNumber>
<PhoneNumber>
<Type>Office</Type>
<Number>3453453453</Number>
</PhoneNumber>
</Numbers>
</Employee>
Desired Result:
<Employee>
<Id>1</Id>
<Numbers>
<Home>1231231231</Home>
<Office>3453453453</Office>
</Numbers>
</Employee>
PhoneNumber Type can be added dynamically like "GuestRoomPhone" etc, so adding properties for each phone number type is not an option.

You can do this by implementing the IXmlSerializable interface on your classes. This allows you to control how the values are written and read.
public class Employee : IXmlSerializable
{
public int Id { get; set; }
public PhoneNumber[] Numbers { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.ReadStartElement("Employee");
reader.ReadStartElement("Id");
Id = reader.ReadContentAsInt();
reader.ReadEndElement(); // Id
reader.ReadStartElement("Numbers");
List<PhoneNumber> numbers = new List<PhoneNumber>();
while (reader.MoveToContent() == XmlNodeType.Element)
{
PhoneNumber num = new PhoneNumber();
num.ReadXml(reader);
numbers.Add(num);
}
Numbers = numbers.ToArray();
reader.ReadEndElement(); // Numbers
reader.ReadEndElement(); // Employee
}
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("Id");
writer.WriteValue(Id);
writer.WriteEndElement();
writer.WriteStartElement("Numbers");
foreach (PhoneNumber num in Numbers)
{
num.WriteXml(writer);
}
writer.WriteEndElement(); // Numbers
}
}
Similarly for the PhoneNumber class.
public class PhoneNumber : IXmlSerializable
{
public string Type { get; set; }
public string Number { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
while (!reader.IsStartElement())
reader.Read();
Type = reader.Name;
Number = reader.ReadElementContentAsString();
}
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement(Type);
writer.WriteString(Number);
writer.WriteEndElement();
}
}

Related

Wrap properties with CData Section - XML Serialization C#

I need to serialize my object in such a way that the properties I want, would get wrapped around CData sections.
I was hoping I could do something like this :
public class Order
{
[JsonProperty]
public int OrderId { get; set; }
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public int Type { get; set; }
[JsonProperty]
public decimal Amount { get; set; }
[JsonProperty]
public DateTime Date { get; set; }
[DataMember]
[JsonProperty]
**[WrapCData]**
public List<Option> ListB { get; set; }
[DataMember]
public List<string> ListC { get; set; }
**[WrapCData]**
public Product Product { get; set; }
}
Is there any attribute or an implementation which could wrap my specific properties around a CData section? Existing StackOverflow answers suggest fiddling with the Entity(Class) itself. This would get really messy.
In the following thread :
How do you serialize a string as CDATA using XmlSerializer?
Philip's answer suggests to make another property and its corresponding CData property. However the property is a string. CreateCDataSection() also takes a string. I need to wrap my custom objects/lists around CDataSections. How can I do that? Any help would be appreciated. Thanks.
Sample XML for the above Order Class:
<Order xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<OrderId>2</OrderId>
<Name>Some Name</Name>
<Type>1</Type>
<Amount>100</Amount>
<Date>2015-12-07T15:10:49.6031106+05:00</Date>
<![CDATA[
<ListB>
<Option>
<OptionValue>OptionValue1</OptionValue>
<OptionName>Option1</OptionName>
</Option>
<Option>
<OptionValue>OptionValue2</OptionValue>
<OptionName>Option2</OptionName>
</Option>
</ListB>
]]>
<ListC>
<string>ListItem1</string>
<string>ListItem2</string>
</ListC>
<![CDATA[
<Product>
<ProductId>1</ProductId>
<Name>ProductName</Name>
<Type>Product Type</Type>
</Product>
]]>
</Order>
With some effort and customization, it possible to get close to what you want, however XmlSerializer will always place the CData nodes at the end of the container element. Your example XML shows the CData nodes between specific nodes of the container element. As long as you don't need this precise control, you can use How do you serialize a string as CDATA using XmlSerializer? to do nested serializations, like so:
public class Order
{
[JsonProperty]
public int OrderId { get; set; }
[JsonProperty]
public string Name { get; set; }
[JsonProperty]
public int Type { get; set; }
[JsonProperty]
public decimal Amount { get; set; }
[JsonProperty]
public DateTime Date { get; set; }
[DataMember]
[JsonProperty]
[XmlIgnore] // Do not serialize directly
[XmlWrapCData] // Instead include in CDATA nodes
public List<Option> ListB { get; set; }
[DataMember]
public List<string> ListC { get; set; }
[XmlIgnore] // Do not serialize directly
[XmlWrapCData] // Instead include in CDATA nodes
public Product Product { get; set; }
[XmlText] // NECESSARY TO EMIT CDATA NODES
[IgnoreDataMember]
[JsonIgnore]
public XmlNode[] CDataContent
{
get
{
return XmlWrapCDataHelper.GetCDataContent(this);
}
set
{
XmlWrapCDataHelper.SetCDataContent(this, value);
}
}
}
public class Product
{
public string ProductId { get; set; }
public string Name { get; set; }
public string Type { get; set; }
}
public class Option
{
public string OptionValue { get; set; }
public string OptionName { get; set; }
}
Using the following extension methods and custom attribute:
[System.AttributeUsage(System.AttributeTargets.Property, AllowMultiple = false)]
public class XmlWrapCDataAttribute : Attribute
{
public XmlWrapCDataAttribute() { this.Namespace = string.Empty; }
public XmlWrapCDataAttribute(string name) : this() { this.Name = name; }
public string Name { get; set; }
public string Namespace { get; set; }
}
public static class XmlWrapCDataHelper
{
static Tuple<PropertyInfo, XmlWrapCDataAttribute> [] XmlWrapCDataProperties(Type type)
{
return type.GetProperties()
.Where(p => p.GetGetMethod() != null && p.GetSetMethod() != null)
.Select(p => Tuple.Create(p, p.GetCustomAttribute<XmlWrapCDataAttribute>()))
.Where(p => p.Item2 != null)
.ToArray();
}
public static XmlNode[] GetCDataContent(object obj)
{
var index = new object[0];
var properties = XmlWrapCDataProperties(obj.GetType());
return properties.Select(p => (XmlNode)p.Item1.GetValue(obj, index).GetCData(p.Item2.Name ?? p.Item1.Name, p.Item2.Namespace)).ToArray();
}
public static void SetCDataContent(object obj, XmlNode [] nodes)
{
if (nodes == null || nodes.Length < 1)
return;
var index = new object[0];
var properties = XmlWrapCDataProperties(obj.GetType()).ToDictionary(p => XName.Get(p.Item2.Name ?? p.Item1.Name, p.Item2.Namespace), p => p);
var xml = "<Root>" + String.Concat(nodes.Select(c => c.Value)) + "</Root>";
foreach (var element in XElement.Parse(xml).Elements())
{
Tuple<PropertyInfo, XmlWrapCDataAttribute> pair;
if (properties.TryGetValue(element.Name, out pair))
{
var value = element.Deserialize(pair.Item1.PropertyType, element.Name.LocalName, element.Name.Namespace.NamespaceName);
pair.Item1.SetValue(obj, value, index);
}
}
}
}
public static class XmlSerializationHelper
{
public static XmlCDataSection GetCData(this object obj, string rootName, string rootNamespace)
{
return obj == null ? null : new System.Xml.XmlDocument().CreateCDataSection(obj.GetXml(XmlSerializerFactory.Create(obj.GetType(), rootName, rootNamespace)));
}
public static XCData GetCData(this object obj, XmlSerializer serializer = null)
{
return obj == null ? null : new XCData(obj.GetXml(serializer));
}
public static string GetXml(this object obj, XmlSerializer serializer = null)
{
using (var textWriter = new StringWriter())
{
var ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
var settings = new XmlWriterSettings() { Indent = true, IndentChars = " ", OmitXmlDeclaration = true }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
(serializer ?? new XmlSerializer(obj.GetType())).Serialize(xmlWriter, obj, ns);
return textWriter.ToString();
}
}
public static object Deserialize(this XContainer element, Type type, string rootName = null, string rootNamespace = null)
{
return element.Deserialize(type, XmlSerializerFactory.Create(type, rootName, rootNamespace));
}
public static object Deserialize(this XContainer element, Type type, XmlSerializer serializer = null)
{
using (var reader = element.CreateReader())
{
return (serializer ?? new XmlSerializer(type)).Deserialize(reader);
}
}
public static T DeserializeXML<T>(this string xmlString, XmlSerializer serializer = null)
{
using (StringReader reader = new StringReader(xmlString))
{
return (T)(serializer ?? new XmlSerializer(typeof(T))).Deserialize(reader);
}
}
}
public static class XmlSerializerFactory
{
readonly static Dictionary<Tuple<Type, string, string>, XmlSerializer> cache;
readonly static object padlock;
static XmlSerializerFactory()
{
padlock = new object();
cache = new Dictionary<Tuple<Type, string, string>, XmlSerializer>();
}
public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace)
{
if (serializedType == null)
throw new ArgumentNullException();
if (rootName == null && rootNamespace == null)
return new XmlSerializer(serializedType);
lock (padlock)
{
XmlSerializer serializer;
var key = Tuple.Create(serializedType, rootName, rootNamespace);
if (!cache.TryGetValue(key, out serializer))
cache[key] = serializer = new XmlSerializer(serializedType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace });
return serializer;
}
}
}
This will parse your provided XML successfully, and in return generate XML that looks like:
<Order>
<OrderId>2</OrderId>
<Name>Some Name</Name>
<Type>1</Type>
<Amount>100</Amount>
<Date>2015-12-07T05:10:49.6031106-05:00</Date>
<ListC>
<string>ListItem1</string>
<string>ListItem2</string>
</ListC><![CDATA[<ListB>
<Option>
<OptionValue>OptionValue1</OptionValue>
<OptionName>Option1</OptionName>
</Option>
<Option>
<OptionValue>OptionValue2</OptionValue>
<OptionName>Option2</OptionName>
</Option>
</ListB>]]><![CDATA[<Product>
<ProductId>1</ProductId>
<Name>ProductName</Name>
<Type>Product Type</Type>
</Product>]]></Order>

XmlSerializer putting element name inside a property when deserializing

I have an issue with the .NET XML Serializer where I have do different XML Elements with different names that map to the same type. Basically, the objects should be exactly the same, but I want them to have a string or enum or something that identifies which of the three possible element names were used. So here's an example:
<Body>
<MyTypeA>
<Foo>bar</Foo>
</MyTypeA>
<MyTypeB>
<Foo>bar</Foo>
</MyTypeB>
</Body>
Now, for the classes, MyTypeA and MyTypeB will both be the same type. For example:
public class Body {
public MyType MyTypeA { get; set; }
public MyType MyTypeB { get; set; }
}
public class MyType {
public string Foo { get; set; }
[XmlIgnore]
public MyTypeType { get; set; }
}
public enum MyTypeType
{
MyTypeA,
MyTypeB
}
When serializing it works fine, because I can always just ensure one way or another that the enum is set properly before serializing. But when deserializing it is not getting set and I'm not sure there's a way how.
For the record, I unfortunately don't get to set the schema, otherwise I would have built it in such a way that I didn't have this problem.
If i understood your question correctly, this might help you. Just write you XML file path on the 4th line and try it.
namespace ConsoleApplication1
{
class Program
{
//private const string xmlPath = #"C:\Users\Jumast\Desktop\StackOverflowQuestion.xml";
private const string xmlPath, // put the file path here
static Body makeBody()
{
var instance1 = new MyType() { Category = Category.MyTypeA, Foo = "bar" };
var instance2 = new MyType() { Category = Category.MyTypeB, Foo = "bar" };
return new Body(){Instance1 = instance1, Instance2 = instance2};
}
static void serializeBody(Body body, string path)
{
var ser = new DataContractSerializer(body.GetType(), body.GetType().Name, "");
using (var w = XmlWriter.Create(path, new XmlWriterSettings() { Indent = true }))
{
ser.WriteObject(w, body);
}
}
static Body deseerializeBody(string xmlPath)
{
Body deserializedBody;
var ser = new XmlSerializer(typeof(Body));
using (Stream stream = File.OpenRead(xmlPath))
{
deserializedBody = (Body)ser.Deserialize(stream);
}
return deserializedBody;
}
static void writeBodyToConsole(Body body)
{
Console.WriteLine("Instance1: " + body.Instance1);
Console.WriteLine("Instance2: " + body.Instance2);
Console.ReadKey();
}
static void Main(string[] args)
{
serializeBody(makeBody(), xmlPath);
writeBodyToConsole(deseerializeBody(xmlPath));
}
}
public class Body : IXmlSerializable
{
#region Properties
public MyType Instance1 { get; set; }
public MyType Instance2 { get; set; }
#endregion
#region IXmlSerializable
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
reader.ReadStartElement();
Instance1 = new MyType(reader);
Instance2 = new MyType(reader);
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
Instance1.WriteXml(writer);
Instance2.WriteXml(writer);
}
#endregion
}
public class MyType : IXmlSerializable
{
#region Fields
private Category _category;
#endregion
#region Constructors
public MyType()
{
_category = Category.nil;
Foo = string.Empty;
}
public MyType(XmlReader reader) { ReadXml(reader);}
#endregion
#region Methods
public override string ToString()
{
var sb = new StringBuilder();
sb.Append(string.Format("Foo = {0}", Foo));
sb.Append(" , ");
sb.Append(string.Format("Category = {0}", Category));
return sb.ToString();
}
#endregion
#region Properties
public string Foo { get; set; }
public Category Category
{
get { return this._category; }
set { this._category = value; }
}
#endregion
#region IXmlSerializable
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
Enum.TryParse(reader.Name, out _category);
reader.Read();
Foo = reader.ReadElementContentAsString("Foo", "");
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement(this.Category.ToString(), "");
writer.WriteElementString("Foo", Foo);
writer.WriteEndElement();
}
#endregion
}
public enum Category
{
MyTypeA,
MyTypeB,
nil
}
}

Class Serialization To XML

Given the following class design:
public class AllUserCollections
{
public List<UserCollection> UserCollections { get; set; }
public AllUserCollections()
{
this.UserCollections = new List<UserCollection> ();
}
}
public class UserCollection
{
public string UserGroup { get; set; }
public Dictionary<int,User> Users { get; set; }
public UserCollection(string userGroup)
{
this.UserGroup = userGroup;
this.Users = new Dictionary<int, User> ();
}
}
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public AgeGroup UserAgeGroup { get; set; }
}
public enum AgeGroup
{
Twenties,
Thirties,
Fourties,
}
How can I serialize this to XML using my existing serialization class?
public static class HardDriveService
{
private static string docsFolderPath = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
private const string fileName = "AllUserCollections.xml";
private static string filePath = Path.Combine(docsFolderPath, fileName);
private static bool FileExists(string fullFilePath)
{
if (File.Exists (fullFilePath))
return true;
return false;
}
public static void Save(AllUserCollections allUserCollections)
{
if (FileExists(filePath))
{
File.Delete (filePath);
}
XmlSerializer serializer = new XmlSerializer(allUserCollections.GetType());
using(StreamWriter writer = new StreamWriter(filePath))
{
serializer.Serialize(writer.BaseStream, allUserCollections);
}
}
public static AllUserCollections Read()
{
AllUserCollections allUserCollections = new AllUserCollections();
XmlSerializer serializer = new XmlSerializer(allUserCollections.GetType());
if (FileExists(filePath))
{
StreamReader reader = new StreamReader(filePath);
object deserialized = serializer.Deserialize(reader.BaseStream);
allUserCollections = (AllUserCollections)deserialized;
}
return allUserCollections;
}
}//End of class.
ISSUES
My code seems to fail on this line -
XmlSerializer serializer = new XmlSerializer(allUserCollections.GetType());
I wonder if it's to do with the class needing to be explicitly marked as "serializable"? How would I do this?
Usage
This code will run on an iphone and save/read directly from an app to XML on the iPhone hard drive.
XMLSerializer doesn't support Dictionary out of the box. Your UserCollection class has a Dictionary. See this link for a workaround.
Why doesn't XmlSerializer support Dictionary?
Other than that the XMLSerializer requires that your classes have default constructors (UserCollection and User don't) and each of them must have the [Serializable] attribute.
You could use XElement to build up an XML format. You could use them along the following lines:
public static XElement ToXml(this User user)
{
if (user == null)
{
throw new ArgumentException("User can not be null.");
}
XElement userElement = new XElement("User");
userElement.Add(new XElement("ID", user.ID));
userElement.Add(new XElement("Name", user.Name));
userElement.Add(new XElement("Location", user.Location));
userElement.Add(new XElement("UserAgeGroup", user.UserAgeGroup));
return userElement;
}
public static string ToXml(this UserCollection userCollection)
{
if (userCollection == null)
{
throw new ArgumentException("UserCollection can not be null.");
}
XElement userCollectionElement = new XElement("UserCollection");
userCollectionElement.Add(new XElement("UserGroup", userCollection.UserGroup));
userCollectionElement.Add(new XElement("Users",
userCollection.Users.Select(x => new XElement("User", x.ToXml()));
return userCollectionElement;
}
Calling .ToString() on an XElement should give you an xml-formatted string.
FULL WORKING SOLUTION
Data Model
using System;
using System.Collections.Generic;
namespace iPhoneHardDriveCRUDPrototype
{
[Serializable]
public class AllUserCollections
{
public List<UserCollection> UserCollections { get; set; }
public AllUserCollections()
{
this.UserCollections = new List<UserCollection> ();
}
}
[Serializable]
public class UserCollection
{
public string UserGroup { get; set; }
public SerializableDictionary<int,User> Users { get; set; }
public UserCollection()
{
this.Users = new SerializableDictionary<int, User> ();
}
public UserCollection(string userGroup)
{
this.UserGroup = userGroup;
this.Users = new SerializableDictionary<int, User> ();
}
}
[Serializable]
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public AgeGroup UserAgeGroup { get; set; }
public User()
{
}
}
[Serializable]
public enum AgeGroup
{
Twenties,
Thirties,
Fourties,
}
}
Serializable Dictionary
using System;
using System.Xml.Serialization;
using System.Collections.Generic;
namespace iPhoneHardDriveCRUDPrototype
{
[XmlRoot("dictionary")]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
bool wasEmpty = reader.IsEmptyElement;
reader.Read();
if (wasEmpty)
return;
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
reader.ReadStartElement("item");
reader.ReadStartElement("key");
TKey key = (TKey)keySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement("value");
TValue value = (TValue)valueSerializer.Deserialize(reader);
reader.ReadEndElement();
this.Add(key, value);
reader.ReadEndElement();
reader.MoveToContent();
}
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
foreach (TKey key in this.Keys)
{
writer.WriteStartElement("item");
writer.WriteStartElement("key");
keySerializer.Serialize(writer, key);
writer.WriteEndElement();
writer.WriteStartElement("value");
TValue value = this[key];
valueSerializer.Serialize(writer, value);
writer.WriteEndElement();
writer.WriteEndElement();
}
}
}//End of Class....
}
Serializer
using System;
using System.IO;
using System.Xml.Serialization;
using System.Reflection;
using System.Collections.Generic;
namespace iPhoneHardDriveCRUDPrototype
{
public static class HardDriveService
{
private static string docsFolderPath = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
private const string fileName = "AllUserCollections.xml";
private static string filePath = Path.Combine(docsFolderPath, fileName);
private static bool FileExists(string fullFilePath)
{
if (File.Exists (fullFilePath))
return true;
return false;
}
public static void Save(AllUserCollections allUserCollections)
{
if (FileExists(filePath))
{
File.Delete (filePath);
}
XmlSerializer serializer = new XmlSerializer(allUserCollections.GetType());
using(StreamWriter writer = new StreamWriter(filePath))
{
serializer.Serialize(writer.BaseStream, allUserCollections);
}
}
public static AllUserCollections Read()
{
AllUserCollections allUserCollections = new AllUserCollections();
XmlSerializer serializer = new XmlSerializer(allUserCollections.GetType());
if (FileExists(filePath))
{
StreamReader reader = new StreamReader(filePath);
object deserialized = serializer.Deserialize(reader.BaseStream);
allUserCollections = (AllUserCollections)deserialized;
}
return allUserCollections;
}
}//End of class.
}
Here you have 2 choices XmlSerializer(which will not work with Dictionary or List type deserialization ) or you can use DataContractSerializer which added in .net 3.0 and has a lot advantages here are few: form this post
optimized for speed (about 10% faster than XmlSerializer, typically)
in "opt-in" - only stuff you specifically mark as [DataMember] will be serialized
doesn't support XML attributes (for speed reasons)
Note : you should add reference to the C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\System.Runtime.Serialization.dll
//to serialize
SerializeHelper.Serialize("your filename" ,new AllUserCollections());
// deserialize
var usertCollections = SerializeHelper.Deserialize<AllUserCollections>("yourfile name");
//code
[DataContract]
public class AllUserCollections
{
public List<UserCollection> UserCollections { get; set; }
public AllUserCollections()
{
this.UserCollections = new List<UserCollection>();
}
}
[DataContract()]
public class UserCollection
{
[DataMember]
public string UserGroup { get; set; }
[DataMember]
public Dictionary<int, User> Users { get; set; }
public UserCollection(string userGroup)
{
this.UserGroup = userGroup;
this.Users = new Dictionary<int, User>();
}
}
[DataContract()]
public class User
{ [DataMember]
public int ID { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string Location { get; set; }
[DataMember]
public AgeGroup UserAgeGroup { get; set; }
}
[DataContract]
public enum AgeGroup
{
Twenties,
Thirties,
Fourties,
}
public static class SerializeHelper
{
public static void Serialize<T>(string fileName, T obj)
{
using (FileStream writer = new FileStream(fileName, FileMode.Create))
{
DataContractSerializer ser =
new DataContractSerializer(typeof(T));
ser.WriteObject(writer, obj);
writer.Close();
}
}
public static T Deserialize<T>(string fileName)
{
T des;
using (FileStream fs = new FileStream(fileName,FileMode.Open))
{
XmlDictionaryReader reader =
XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas());
DataContractSerializer ser = new DataContractSerializer(typeof(T));
des =
(T)ser.ReadObject(reader, true);
reader.Close();
fs.Close();
}
return des;
}
}

How to make following class objects to xml?

I have following class. (SIMModel, Product, Item)
public class SIMModel
{
public Product Product { get; set; }
}
public class Product
{
public List<Item> Items { get; set; }
}
public class Item
{
[XmlAttribute("ID")]
public String ID { get; set; }
[XmlAttribute("Name")]
public String Name { get; set; }
public Child_Item Child_Item { get; set; }
public Parent_Item Parent_Item { get; set; }
}
public class Child_Item
{
[XmlAttribute("ID")]
public String ID { get; set; }
}
And I want to make this XML
<SIMModel>
<Product>
<Item ID="N" Name="N-1">
<Child_Item ID="N-1-1">
</Item>
</Proudct>
</SIMModel>
How can i make the Simulation XML using upper class?
I don't know how to wrap each class..
I made a simple Serialize method that serializes to string and removes the namespaces.
private static void Main(string[] args)
{
string result = Serialize(new SIMModel
{
Product = new Product
{
Items = new List<Item>
{
new Item
{
ID = "N",
Name = "N-1",
Child_Item = new Child_Item {ID = "N-1-1"}
}
}
}
});
Console.WriteLine(result);
}
public static string Serialize<T>(T value)
{
if (value == null)
{
return null;
}
//Create our own namespaces for the output
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
//Add an empty namespace and empty value
ns.Add("", "");
XmlSerializer serializer = new XmlSerializer(typeof (T));
XmlWriterSettings settings = new XmlWriterSettings
{
Encoding = new UnicodeEncoding(false, false),
Indent = true,
OmitXmlDeclaration = true
};
using (StringWriter textWriter = new StringWriter())
{
using (XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings))
{
serializer.Serialize(xmlWriter, value, ns);
}
return textWriter.ToString();
}
}
Output:
<SIMModel>
<Product>
<Items>
<Item ID="N" Name="N-1">
<Child_Item ID="N-1-1" />
</Item>
</Items>
</Product>
</SIMModel>

correct serialize but incorrect Deserialize

consider following codes and classes:
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
namespace ConsoleApplication1
{
public class Element1
{
[XmlAttribute]
public int Order { get; set; }
public string name1 { get; set; }
public ElementCollcetion collection { get; set; }
}
public class Element2
{
[XmlAttribute]
public int Order { get; set; }
public string name2 { get; set; }
}
public class Elements
{
public Element1 element1 { get; set; }
public Element2 element2 { get; set; }
}
public interface IFoo
{
string FooName { get; set; }
}
public class Foo1 : IFoo
{
public string FooName { get; set; }
public int deff1 { get; set; }
}
public class Foo2 : IFoo
{
public string FooName { get; set; }
public bool deff2 { get; set; }
}
public class ElementCollcetion : List<IFoo>, IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
XmlSerializer serializer = null;
bool flag;
reader.Read();
while (true)
{
flag = false;
if (string.Compare(reader.Name, typeof(Foo1).Name) == 0)
{
serializer = new XmlSerializer(typeof(Foo1));
flag = true;
}
else if (string.Compare(reader.Name, typeof(Foo2).Name) == 0)
{
serializer = new XmlSerializer(typeof(Foo2));
flag = true;
}
if (flag)
this.Add((IFoo)serializer.Deserialize(reader));
else
break;
}
}
public void WriteXml(System.Xml.XmlWriter writer)
{
foreach (IFoo foo in this.AsEnumerable())
{
XmlSerializer serializer = new XmlSerializer(foo.GetType());
serializer.Serialize(writer, foo);
}
}
}
class Program
{
static void Main(string[] args)
{
Elements elements = new Elements()
{
element1 = new Element1
{
name1 = "Name1",
Order = 1,
collection = new ElementCollcetion(){
new Foo1{deff1=10,FooName="FooName1"},
new Foo2{deff2=true,FooName="FooName2"}
},
},
element2 = new Element2
{
name2 = "Name2",
Order = 2
}
};
XmlSerializer serializer = new XmlSerializer(typeof(Elements));
TextWriter textWriter = new StreamWriter(#"d:\ser.xml");
serializer.Serialize(textWriter, elements);
textWriter.Close();
TextReader textReader = new StreamReader(#"d:\ser.xml");
Elements element = (Elements)serializer.Deserialize(textReader);
textReader.Close();
}
}
}
when i run it, an xml will generated into ser.xml like so:
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<element1 Order="1">
<name1>Name1</name1>
<collection>
<Foo1>
<FooName>FooName1</FooName>
<deff1>10</deff1>
</Foo1>
<Foo2>
<FooName>FooName2</FooName>
<deff2>true</deff2>
</Foo2>
</collection>
</element1>
<element2 Order="2">
<name2>Name2</name2>
</element2>
</Elements>
but it cannot correctly Deserialize the file unless i reorder the elements in the xml like so:
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<element2 Order="2">
<name2>Name2</name2>
</element2>
<element1 Order="1">
<name1>Name1</name1>
<collection>
<Foo1>
<FooName>FooName1</FooName>
<deff1>10</deff1>
</Foo1>
<Foo2>
<FooName>FooName2</FooName>
<deff2>true</deff2>
</Foo2>
</collection>
</element1>
</Elements>
note that serializer.UnknownAttribute and serializer.UnknownElement will not raise during both execution.
what is the problem? and how can i fix it?
---------------EDIT----------------------
i know that problem is in IXmlSerializable.ReadXml() implementation. but what kind of problem and how should i cure it?
Basically, you aren't progressing the reader to the end of the sub-tree correctly. Adding reader.Read(); to the end of ReadXml fixes it, but is a bit ugly; ReadSubtree() may be safer.
Frankly, implementing IXmlSerializable correctly and robustly is hard. I always advise against it.
Although I would have to see your IXmlSerializable implementation, my bet would be, that your ReadXml implementation has a reversed order of processing ... e.g.: it first looks for element2 instead of element1. If this is not it, please post your IXmlSerializable implementation.
EDIT
As Marc pointed out, you need to add another read. Problem was, that XmlReader by calling reader.Read() processed collection entry tag, but at the end of your method, you didn't process the end tag /collection, hence another reader.Read() call. This basically prevented the deserialization to proceed correctly.
Generally the correct pattern for ReadXml implementation is to start with:
bool isEmpty = reader.IsEmptyElement;
reader.ReadStartElement(); //Start reading the element
if (isEmpty) //Return on empty element
{
return;
}
And finish with:
reader.ReadEndElement();
Here is also your implementation using the pattern:
public void ReadXml(System.Xml.XmlReader reader)
{
XmlSerializer serializer = null;
bool flag;
bool isEmpty = reader.IsEmptyElement;
reader.ReadStartElement();
if (isEmpty)
{
return;
}
while (true)
{
flag = false;
if (string.Compare(reader.Name, typeof(Foo1).Name) == 0)
{
serializer = new XmlSerializer(typeof(Foo1));
flag = true;
}
else if (string.Compare(reader.Name, typeof(Foo2).Name) == 0)
{
serializer = new XmlSerializer(typeof(Foo2));
flag = true;
}
if (flag)
this.Add((IFoo)serializer.Deserialize(reader));
else
break;
}
reader.ReadEndElement();
}

Categories