How do I control the element name when serializing child classes? - c#

If I have something like:
public abstract class Animal { }
[XmlRoot]
public class Dog:Animal { }
[XmlRoot]
Public class Cat:Animal { }
How do I go about serializing (and then be able to de-serialize) a Cat object like:
<Cat> </Cat>
and not something like:
<Animal type="Cat"> </Animal>
I've tried using different combinations from the System.Xml.Serialization and System.Runtime.Serialization namespaces, but I can't seem to get it.
I can get a serialized object to look the way I want by specifying the type of the object in the serializer. This works for serialization, but not for de-serialization..because I don't know the type of object in the xml.
One possible solution would be:
public abstract class Animal
{
static Dictionary<String,Type> typeDic;
static Animal()
{
typeDic = new Dictionary<string, Type>();
//Get classes in the same namespace of this object
Type ctype = typeof(Animal);
Type[] types = ctype.Assembly.GetTypes().Where(t => String.Equals(t.Namespace, ctype.Namespace, StringComparison.Ordinal)).ToArray();
//For any XmlRootAttribute objects, add the ElementName and Type to a dictionary
foreach(Type type in types)
{
foreach (XmlRootAttribute xmlRoot in type.GetCustomAttributes(typeof(XmlRootAttribute), false))
{
typeDic.Add(xmlRoot.ElementName, type);
}
}
}
public static Content read(String XML)
{
XmlSerializer s = null;
//check the first element to see what the name is, then create the serializer using the type from the dictionary
XmlReader reader = XmlReader.Create(GenerateStreamFromString(XML));
reader.Read();
if (reader.Name == "xml")
{
while (reader.MoveToContent() != XmlNodeType.Element) { }
}
if (typeDic.ContainsKey(reader.Name))
s = new XmlSerializer(typeDic[reader.Name]);
else
throw new Exception("Unknown Type in read");
return (Content)s.Deserialize(reader);
}
public static string write<T>(T f)
{
XmlSerializer s = new XmlSerializer(typeof(T));
MemoryStream stream = new MemoryStream();
s.Serialize(stream, f);
stream.Position = 0;
return StreamToString(stream);
}
}
[XmlRoot(ElementName="Dog")]
public class Dog:Animal { }
[XmlRoot(ElementName="Cat")]
Public class Cat:Animal { }

I tried this for a long time and could not figure it out either. the only thing i could come up with is using some reflection and generating the doc myself:
class Program
{
static void Main(string[] args)
{
List<Animal> animals = new List<Animal>
{
new Dog{Name = "Ernie", HasFleas = true},
new Cat{ Name = "Bert", Collar = "Blue with a little bell" }
};
XDocument doc = new XDocument();
doc.Declaration = new XDeclaration("1.0","utf-8","true");
doc.Add(new XElement("root", animals.Select(animal => animal.GetElement)));
Console.WriteLine(doc);
}
}
public abstract class Animal
{
[XmlAttribute]
public string Name { get; set; }
public XElement GetElement
{
get
{
Type type = this.GetType();
XElement result = new XElement(type.Name);
foreach (PropertyInfo property in
type.GetProperties().Where(pi=> pi.CustomAttributes.Any(ca=> ca.AttributeType == typeof(System.Xml.Serialization.XmlAttributeAttribute))))
{
result.Add(new XAttribute(property.Name,property.GetValue(this)));
}
foreach (PropertyInfo property in
type.GetProperties().Where(pi => pi.CustomAttributes.Any(ca => ca.AttributeType == typeof(System.Xml.Serialization.XmlElementAttribute))))
{
result.Add(new XElement(property.Name,property.GetValue(this)));
}
return result;
}
}
}
public class Dog : Animal
{
[XmlAttribute]
public bool HasFleas { get; set; }
}
public class Cat : Animal
{
[XmlElement]
public string Collar { get; set; }
}
To deserialize you have to do the other way round so some form of factory.

Related

Can you deserialize and older version of xml into a newer structure

I am attempting to update the objects that being deserialized. Taking one object, renaming, using parts and adding a new object that will hold the remaining objects. Is there a way in .NET to deserialize the old XML into the new format?
e.g.
Old Structure:
<objectinfo>
<element1></element1>
<element2></element2>
<element3></element3>
<element4></element4>
</objectinfo>
New Structure:
<objinfo>
<element1></element1>
<element2></element2>
</objinfo>
<newobject>
<element3></element3>
<element4></element4>
</newobject>
Note I am using XmlSerializer to deserialize.
Assuming you are using XmlSerializer to deserialize, if your <objectinfo> is an immediate child of the root XML element, then deserializing to some DTO type identical to old type and mapping to the new object manually or via automapper solves the problem quite easily.
If, however, the objects being modified are nested deeply inside an object hierarchy being deserialized then the DTO strategy isn't as convenient because XmlSerializer doesn't offer a general surrogate DTO replacement mechanism. One alternative approach in such situations is to manually handle the unknown elements in an XmlSerializer.UnknownElement event.
To do this in a general way, introduce the following interface and extension methods for XML deserialization:
public interface IUnknownElementHandler
{
void OnUnknownElement(object sender, XmlElementEventArgs e);
}
public static partial class XmlSerializationHelper
{
public static T LoadFromXml<T>(this string xmlString, XmlSerializer serializer = null)
{
serializer = serializer ?? new XmlSerializer(typeof(T)).AddUnknownElementHandler();
using (var reader = new StringReader(xmlString))
return (T)serializer.Deserialize(reader);
}
public static T LoadFromFile<T>(string filename, XmlSerializer serializer = null)
{
serializer = serializer ?? new XmlSerializer(typeof(T)).AddUnknownElementHandler();
using (var reader = new FileStream(filename, FileMode.Open))
return (T)serializer.Deserialize(reader);
}
public static XmlSerializer AddUnknownElementHandler(this XmlSerializer serializer)
{
serializer.UnknownElement += (o, e) =>
{
var handler = e.ObjectBeingDeserialized as IUnknownElementHandler;
if (handler != null)
handler.OnUnknownElement(o, e);
};
return serializer;
}
}
Then, assuming your new data model looks e.g. like this, where Root is the top-level object and ContainerType contains the elements being restructured:
[XmlRoot(ElementName = "Root")]
public class Root
{
public ContainerType ContainerType { get; set; }
}
[XmlRoot(ElementName = "ContainerType")]
public partial class ContainerType
{
[XmlElement(ElementName = "objinfo")]
public Objinfo Objinfo { get; set; }
[XmlElement(ElementName = "newobject")]
public Newobject Newobject { get; set; }
}
[XmlRoot(ElementName = "objinfo")]
public class Objinfo
{
[XmlElement(ElementName = "element1")]
public string Element1 { get; set; }
[XmlElement(ElementName = "element2")]
public string Element2 { get; set; }
}
[XmlRoot(ElementName = "newobject")]
public class Newobject
{
[XmlElement(ElementName = "element3")]
public string Element3 { get; set; }
[XmlElement(ElementName = "element4")]
public string Element4 { get; set; }
}
Add an OnUnknownElement handler to ContainerType as follows:
public partial class ContainerType : IUnknownElementHandler
{
#region IUnknownElementHandler Members
void IUnknownElementHandler.OnUnknownElement(object sender, XmlElementEventArgs e)
{
var container = (ContainerType)e.ObjectBeingDeserialized;
var element1 = e.Element.SelectSingleNode("element1");
var element2 = e.Element.SelectSingleNode("element2");
if (element1 != null || element2 != null)
{
container.Objinfo = container.Objinfo ?? new Objinfo();
if (element1 != null)
container.Objinfo.Element1 = element1.InnerText;
if (element2 != null)
container.Objinfo.Element2 = element2.InnerText;
}
var element3 = e.Element.SelectSingleNode("element3");
var element4 = e.Element.SelectSingleNode("element4");
if (element3 != null || element4 != null)
{
container.Newobject = container.Newobject ?? new Newobject();
if (element3 != null)
container.Newobject.Element3 = element3.InnerText;
if (element4 != null)
container.Newobject.Element4 = element4.InnerText;
}
}
#endregion
}
Then when you deserialize your Root from a file using the LoadFromFile method above:
var root = XmlSerializationHelper.LoadFromFile<Root>(filename);
The obsolete, unknown XML elements will postprocessed by the ContainerType handler.
Demo fiddle here.

XmlSerializer that serializes all and only nested value types properties

I need to serialize a class so that the serialization will include all nested value types properties.
I found it somewhat hard to generalize it in English (Not a native speaker, so an edit to the wording is welcomed), so I'll explain:
If the property is of a value type - serialize its name and value
If the property is of a Nullable type: If its value is non-null, do as above (Effectively, serialize the Nullable's Value property); else, don't serialize it.
If the property is of a class type, serialize the class' properties according to the above, and do not serialize the class name.
For example, this:
public class SerializeMe
{
public int A { get; set; }
public int? B { get; set; }
public int? C { get; set; }
public MyClass MyClass { get; set;}
}
public class MyClass
{
public int Z { get; set;}
}
If instantiated like this:
public static void Main()
{
var instance = new SerializeMe
{
A = 1,
B = 3,
MyClass = new MyClass { Z = 2},
});
}
Should be serialized like this:
<SerializeMe>
<A>1</A>
<B>3</B>
<Z>2</Z>
</SerializeMe>
But I don't know how to do the last bullet, and I end with:
<SerializeMe>
<A>1</A>
<B>3</B>
<UndesiredTag><Z>2</Z></UndesiredTag>
</SerializeMe>
Now, the last bullet requirement invites recursion, but as I understand from this answer, it's the parent class' WriteXml that may be able to omit the <UndesiredTag> tag, while the nested class can't.
So, what I currently have (fiddle):
using System;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.IO;
public class SerializeMe : IXmlSerializable
{
public int A { get; set; }
public int? B { get; set; }
public int? C { get; set; }
public MyClass MyClass { get; set;}
public void WriteXml(XmlWriter writer)
{
Program.WriteXml<SerializeMe>(writer, this);
}
public void ReadXml(XmlReader reader) {}
public XmlSchema GetSchema() { return null; }
}
[AttributeUsage(AttributeTargets.Class)]
public class Nested : Attribute
{}
[Nested]
public class MyClass : IXmlSerializable
{
public int Z { get; set;}
public void WriteXml(XmlWriter writer)
{
Program.WriteXml<MyClass>(writer, this);
}
public void ReadXml(XmlReader reader) {}
public XmlSchema GetSchema() { return null; }
}
public class Program
{
public static void Main()
{
var s = XmlSerialize<SerializeMe>(new SerializeMe
{
A = 1,
B = 3,
MyClass = new MyClass { Z = 2},
});
Console.WriteLine(s);
}
public static string XmlSerialize<T>(T entity) where T : class
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
XmlSerializer xsSubmit = new XmlSerializer(typeof(T));
StringWriter sw = new StringWriter();
using (XmlWriter writer = XmlWriter.Create(sw, settings))
{
var xmlns = new XmlSerializerNamespaces();
xmlns.Add(string.Empty, string.Empty);
xsSubmit.Serialize(writer, entity, xmlns);
return sw.ToString();
}
}
public static void WriteXml<T>(XmlWriter writer, T obj)
{
PropertyInfo[] props = obj.GetType().GetProperties();
foreach (var prop in props)
{
var val = prop.GetValue(obj);
if (val != null)
{
if (prop.PropertyType.IsValueType ||
prop.PropertyType.IsGenericType &&
prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
writer.WriteElementString(prop.Name, val.ToString());
}
else
{
if (prop.PropertyType.GetCustomAttribute(typeof(Nested)) != null)
{
writer.WriteStartElement("UndesiredTag"); // If only I could use an empty string...
((dynamic)val).WriteXml(writer);
writer.WriteEndElement();
}
}
}
}
}
}
Note that my current code assumes only one level of nesting. If you think you can solve my issue using a recursion, it would be better - since you'd allow multiple nesting levels.
Since you override all default serialization anyway, it seems simpler to me to just ditch XmlSerializer and do it completely on your own.
public static void Main()
{
var s = XmlSerialize(new SerializeMe
{
A = 1,
B = 3,
MyClass = new MyClass { Z = 2 },
});
Console.WriteLine(s);
}
public static string XmlSerialize(object entity)
{
var buf = new StringBuilder();
using (var writer = XmlWriter.Create(buf, new XmlWriterSettings() {
OmitXmlDeclaration = true,
Indent = true
}))
{
WriteElement(writer, entity);
}
return buf.ToString();
}
static void WriteElement(XmlWriter writer, object obj)
{
writer.WriteStartElement(obj.GetType().Name);
WriteElementProperties(writer, obj);
writer.WriteEndElement();
}
static void WriteElementProperties(XmlWriter writer, object obj)
{
foreach (var prop in obj.GetType().GetProperties())
{
var val = prop.GetValue(obj);
if (val != null)
{
if (prop.PropertyType.IsValueType ||
prop.PropertyType.IsGenericType &&
prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
writer.WriteElementString(prop.Name, val.ToString());
}
else
{
if (prop.PropertyType.GetCustomAttribute(typeof(Nested)) != null)
{
WriteElementProperties(writer, val);
} else {
WriteElement(writer, val);
}
}
}
}
}

How to achieve ideal json serializing/deserializing in C# UWP?

Note: The question is restricted to C# UWP.
The Dream:
public static class Serializer {
// return json string
public string Serialize(object obj) { ??? }
// return T from json string
public T Deserialize<T>(string jsonString) { ??? }
}
Closest I've Come:
public static class Serializer
{
public static string Serialize(object obj, DataContractJsonSerializerSettings settings=null)
{
if (obj == null) {
throw new NullReferenceException();
}
settings = settings ?? new DataContractJsonSerializerSettings();
DataContractJsonSerializer jsonizer = new DataContractJsonSerializer(obj.GetType(), settings);
string jsonString = null;
using ( MemoryStream stream = new MemoryStream() )
{
jsonizer.WriteObject(stream, obj);
stream.Position = 0;
StreamReader sr = new StreamReader(stream);
jsonString = sr.ReadToEnd();
}
return jsonString;
}
public static T Deserialize<T>(string jsonString)
{
DataContractJsonSerializer jsonizer = new DataContractJsonSerializer(typeof(T));
T obj;
using (Stream stream = GenerateStreamFromString(jsonString))
{
obj = (T)jsonizer.ReadObject(stream);
}
return obj;
}
private static Stream GenerateStreamFromString(string s)
{
MemoryStream stream = new MemoryStream();
StreamWriter writer = new StreamWriter(stream);
writer.Write(s);
writer.Flush();
stream.Position = 0;
return stream;
}
}
The Problem
The partial solution I posted works in simple cases. However, it fails when the subtype of the object being deserialized is difficult (or impossible) to determine from the json string. For instance,
IList<Animal> animals = new List<Animal>();
animals.add(new Dog("Woofy"));
animals.add(new Cat("Fluffy"));
string json = Serializer.Serialize(animals);
IList<Animal> result = Serializer.Deserialize<List<Animal>>(json);
// ^ fails because subtype information was lost during serialization
bool succeeded = result.get(0).Name.Equals("Woofy") && result.get(1).Name.Equals("Fluffy");
What I'm Looking For:
An implementation of the skeleton specified in "The Dream" which passes the driver specified in "The Problem". Comments welcome.
Your Serializer works perfectly fine, if you add the KnownType-attributes to your base class:
[DataContract]
[KnownType(typeof(Dog))] // add these
[KnownType(typeof(Cat))] // lines
public class Animal
{
[DataMember]
public string Name { get; set; }
}
[DataContract]
public class Dog : Animal
{
}
[DataContract]
public class Cat : Animal
{
}
It's necessary for the DataContractJsonSerializer to preserve the type-information of the instances being serialized. You can see this in the resulting serialized JSON:
[{\"__type\":\"Dog:#My.Serialization.Sample.Project\",\"Name\":\"Woofy\"},{\"__type\":\"Cat:#My.Serialization.Sample.Project\",\"Name\":\"Fluffy\"}]
There is the extra key __type which holds the concrete information that the first object is a Dog form namespace My.Serialization.Sample.Project.
But as #dbc already mentioned, you might be slightly better off using JSON.NET which easily allows you to serialize your list without having the need to decorate your data transfer object with all those attributes. Even DataContract and DataMember are not needed.
public class Animal
{
public string Name { get; set; }
}
public class Dog : Animal { }
public class Cat : Animal { }
Using it this way
var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto };
string jsonNet = JsonConvert.SerializeObject(animals, settings);
var jsonNetResult = JsonConvert.DeserializeObject<IList<Animal>>(jsonNet);
yields that result:
[{\"$type\":\"My.Serialization.Sample.Project.Dog, My.Assembly\",\"Name\":\"Woofy\"},{\"$type\":\"My.Serialization.Sample.Project.Cat, My.Assembly\",\"Name\":\"Fluffy\"}]
With JsonSubTypes you have at least two possibilities:
[JsonConverter(typeof(JsonSubtypes))]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Dog), "HadWalkToday")]
[JsonSubtypes.KnownSubTypeWithProperty(typeof(Cat), "ColorOfWhiskers")]
public class Animal
{
public string Name { get; set; }
}
public class Dog : Animal
{
public bool HadWalkToday { get; set; }
}
public class Cat : Animal
{
public string ColorOfWhiskers { get; set; }
}
or
[JsonConverter(typeof(JsonSubtypes), "Sound")]
[JsonSubtypes.KnownSubType(typeof(Dog), "Bark")]
[JsonSubtypes.KnownSubType(typeof(Cat), "Meow")]
public class Animal
{
public virtual string Sound { get; }
public string Color { get; set; }
}
public class Dog : Animal
{
public override string Sound { get; } = "Bark";
public string Breed { get; set; }
}
public class Cat : Animal
{
public override string Sound { get; } = "Meow";
public bool Declawed { get; set; }
}
That works with:
[Test]
public void Proof()
{
Dog dog = new Dog()
{
Name = "Woofy",
HadWalkToday = true
};
Cat cat = new Cat()
{
Name = "Fluffy",
ColorOfWhiskers = "Brown"
};
IList<Animal> animals = new List<Animal>()
{
dog,
cat
};
string json = JsonConvert.SerializeObject(animals);
IList<Animal> result = JsonConvert.DeserializeObject<List<Animal>>(json);
Assert.IsTrue(result[0].GetType() == typeof(Dog));
Assert.IsTrue(result[1].GetType() == typeof(Cat));
}
The answer I arrived at:
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace SerializationDemo
{
class Program
{
static void Main(string[] args)
{
Dog dog = new Dog()
{
Name = "Woofy",
HadWalkToday = true
};
Cat cat = new Cat()
{
Name = "Fluffy",
ColorOfWhiskers = "Brown"
};
IList<Animal> animals = new List<Animal>()
{
dog,
cat
};
string json = Serializer.Serialize(animals);
IList<Animal> result = Serializer.Deserialize<List<Animal>>(json);
bool serializerSuccessful = dog.Equals(result[0]) && cat.Equals(result[1]);
}
}
public class Animal
{
public string Name { get; set; }
public override bool Equals(object obj)
{
var animal = obj as Animal;
return this.Name == animal.Name;
}
}
public class Dog : Animal
{
public bool HadWalkToday { get; set; }
public override bool Equals(object obj)
{
var dog = obj as Dog;
return this.HadWalkToday == dog.HadWalkToday && base.Equals(obj);
}
}
public class Cat : Animal
{
public string ColorOfWhiskers { get; set; }
public override bool Equals(object obj)
{
var cat = obj as Cat;
return this.ColorOfWhiskers == cat.ColorOfWhiskers && base.Equals(obj);
}
}
public static class Serializer
{
private static readonly JsonSerializerSettings settings = new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.All,
Formatting = Formatting.Indented
};
public static string Serialize(object obj)
{
if (obj == null)
{
throw new NullReferenceException();
}
string jsonString = JsonConvert.SerializeObject(obj, settings);
return jsonString;
}
public static T Deserialize<T>(string jsonString)
{
T obj = JsonConvert.DeserializeObject<T>(jsonString, settings);
return obj;
}
}
}

Changing type of element in XML Serialization

I am having huge problems with XML serialization. I have two classes, both need to be serializeable. In the inherited class, I would like to change the serialization behavior, so that a string property gets serialized as complex type.
public class Animal
{
[XmlElement(ElementName = "NAME")]
public string Name { get; set; }
public virtual bool ShouldSerializeName() { return true; }
}
public class Cat : Animal
{
public override bool ShouldSerializeName() { return false; }
[XmlElement(ElementName = "NAME")]
public NameAndType Name2 { get; set; }
}
public class NameAndType
{
public string Name { get; set; }
public string Type { get; set; }
}
...
var cat = new Cat {Name2 = new NameAndType {Name = "LittleCat"}};
new XmlSerializer(typeof(Cat)).Serialize(Console.Out, cat);
I have tried different approaches, but I didn't find a way to change how the NAME element get's serialized.
With the example above, I get the error message:
The XML element 'NAME' from namespace '' is already present in the current scope. Use XML attributes to specify another XML name or namespace for the element.
The reason you get the error is that, during XmlSerializer code generation, the code generator doesn't understand that the two potential NAME elements on Cat will never be simultaneously serialized, so throws the exception.
Instead, you can apply XmlAnyElementAttribute to a virtual property returning an XElement, then manually create and return an appropriate XElement for the name for each class in the hierarchy:
[XmlInclude(typeof(Cat))]
public class Animal
{
[XmlIgnore]
public string Name { get; set; }
[XmlAnyElement]
public virtual XElement XmlName
{
get
{
return Name == null ? null : new XElement("NAME", Name);
}
set
{
Name = (value == null ? null : value.Value);
}
}
}
public class Cat : Animal
{
// Must be cached as per https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer%28v=vs.110%29.aspx
static XmlSerializer nameSerializer;
static Cat()
{
nameSerializer = new XmlSerializer(typeof(NameAndType), new XmlRootAttribute("NAME"));
}
[XmlIgnore]
public NameAndType Name2 { get; set; }
[XmlAnyElement]
public override XElement XmlName
{
get
{
return (Name2 == null ? null : XObjectExtensions.SerializeToXElement(Name2, nameSerializer, true));
}
set
{
Name2 = (value == null ? null : XObjectExtensions.Deserialize<NameAndType>(value, nameSerializer));
}
}
}
Using the extension methods:
public static class XObjectExtensions
{
public static T Deserialize<T>(this XContainer element)
{
return element.Deserialize<T>(new XmlSerializer(typeof(T)));
}
public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
{
using (var reader = element.CreateReader())
{
object result = serializer.Deserialize(reader);
if (result is T)
return (T)result;
}
return default(T);
}
public static XElement SerializeToXElement<T>(this T obj)
{
return obj.SerializeToXElement(new XmlSerializer(obj.GetType()), true);
}
public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, bool omitStandardNamespaces)
{
var doc = new XDocument();
using (var writer = doc.CreateWriter())
{
XmlSerializerNamespaces ns = null;
if (omitStandardNamespaces)
(ns = new XmlSerializerNamespaces()).Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
serializer.Serialize(writer, obj, ns);
}
var element = doc.Root;
if (element != null)
element.Remove();
return element;
}
}
Which, for a List<Animal>, produces XML like this:
<ArrayOfAnimal xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Animal>
<NAME>duck</NAME>
</Animal>
<Animal xsi:type="Cat">
<NAME>
<Name>Smokey</Name>
<Type>Siamese</Type>
</NAME>
</Animal>
</ArrayOfAnimal>
You're specifying two different XML elements with the same name on the same graph level, which is not allowed.
I will offer a solution, but it involves concatenating type and name for the Cat class serialization. If it is not imperative to serialize that NameAndType class, then go on:
On Animal, set Name to be virtual;
On Cat, set XmlIgnore on Name2. Then override Name and return both properties of Name2 the way you fancy.
If you really need to serialize that class as it is, then I'm afraid you'll have to so it with a different elementName.
Edit: Sample code:
public class Animal
{
[XmlElement(ElementName = "NAME")]
public virtual string Name { get; set; }
public virtual bool ShouldSerializeName() { return true; }
}
public class Cat : Animal
{
public override bool ShouldSerializeName() { return false; }
[XmlIgnore]
public NameAndType Name2 { get; set; }
[XmlElement(ElementName = "NAME")]
public override string Name
{
get
{
return String.Format("{0} [Type:{1}]", Name2.Name, Name2.Type);
}
set { }
}
}

XmlAttribute / XmlElement in Interface (Element Name not changed when Serialized) - Why?

When I add an "XmlAttribute/XmlElement" to a property in an Interface and 100 other classes inherit from this interface, when Serializing the object to XML - why doesn't the attributename I entered show up in the XML file after serialization?
interface Test
{
[XmlAttribute("Name")]
bool PropertyName { get; set; }
}
when saving the file, is shows "PropertyName" and not "Name".
Is there any way to make it work, so a proeprty with an XmlAttributes added to an interface changes the Value everywhere instead of Value because if a few classes inherit from the same interface it takes alot of time to add all those Attributes to all those classes that inherit it, and it would be easier to change the Name of the Attribute instead of changing a 100 of them.
Deukalin, spent some time and make it work with comprehensive tree of classes and interfaces.
Here is the working code
public interface ITestX
{
[XmlAttribute("NameX")]
string PropertyNameX { get; set; }
}
public interface ITestY
{
[XmlAttribute("NameY")]
string PropertyNameY { get; set; }
}
public interface ITestZ
{
[XmlAttribute("NameZ")]
string PropertyNameZ { get; set; }
}
public abstract class TestC : ITestZ
{
public abstract string PropertyNameZ { get; set; }
}
public abstract class TestA : TestC, ITestX, ITestY
{
public abstract string PropertyNameX { get; set; }
public abstract string PropertyNameY { get; set; }
}
public class TestB : TestA
{
public override string PropertyNameX { get; set; }
public override string PropertyNameY { get; set; }
public override string PropertyNameZ { get; set; }
}
public static class ClassHandler
{
public static T GetCustomAttribute<T>(this PropertyInfo propertyInfo, bool inherit) where T : Attribute
{
object[] attributes = propertyInfo.GetCustomAttributes(typeof(T), inherit);
return attributes == null || attributes.Length == 0 ? null : attributes[0] as T;
}
public static void GetXmlAttributeOverrides(XmlAttributeOverrides overrides, Type type)
{
if (type.BaseType != null)
{
GetXmlAttributeOverrides(overrides, type.BaseType);
}
foreach (Type derived in type.GetInterfaces())
{
foreach (PropertyInfo propertyInfo in derived.GetProperties())
{
XmlAttributeAttribute xmlAttributeAttribute = ClassHandler.GetCustomAttribute<XmlAttributeAttribute>(propertyInfo, true) as XmlAttributeAttribute;
if (xmlAttributeAttribute == null)
continue;
XmlAttributes attr1 = new XmlAttributes();
attr1.XmlAttribute = new XmlAttributeAttribute();
attr1.XmlAttribute.AttributeName = xmlAttributeAttribute.AttributeName;
overrides.Add(type, propertyInfo.Name, attr1);
}
}
}
}
class Program
{
static void Main(string[] args)
{
XmlAttributeOverrides XmlAttributeOverrides = new XmlAttributeOverrides();
ClassHandler.GetXmlAttributeOverrides(XmlAttributeOverrides, typeof(TestB));
try
{
TestB xtest = new TestB() { PropertyNameX = "RajX", PropertyNameY = "RajY", PropertyNameZ = "RajZ" };
StringBuilder xmlString = new StringBuilder();
using (XmlWriter xtw = XmlTextWriter.Create(xmlString))
{
XmlSerializer serializer = new XmlSerializer(typeof(TestB), XmlAttributeOverrides);
serializer.Serialize(xtw, xtest);
xtw.Flush();
}
Console.WriteLine(xmlString.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
Below is the output of sample above
<?xml version="1.0" encoding="utf-16"?><TestB xmlns:xsi="http://www.w3.org/2001/
XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" NameZ="RajZ" Na
meX="RajX" NameY="RajY" />
Press any key to continue . . .
I altered the code to suit my project more. What I did was this:
public static XmlAttributeOverrides GetXmlAttributeOverrides(Type type)
{
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
foreach (Type derived in ClassHandler.GetImplementedInterfaces(type))
{
foreach (PropertyInfo propertyInfo in derived.GetProperties())
{
XmlAttributeAttribute xmlAttributeAttribute = ClassHandler.GetCustomAttribute<XmlAttributeAttribute>(propertyInfo, true) as XmlAttributeAttribute;
if (xmlAttributeAttribute == null) continue;
XmlAttributes attr1 = new XmlAttributes();
attr1.XmlAttribute = new XmlAttributeAttribute();
attr1.XmlAttribute.AttributeName = xmlAttributeAttribute.AttributeName;
overrides.Add(type, propertyInfo.Name, attr1);
}
}
return overrides;
}
The object I'm trying on which implements an inteface with properties all have "[XmlAttributeAttribute(SomeName)] over them.
Still, when I serialize it gives the same results. I don't get the Attribute value from the interface.
This is how I serialize:
public static void SerializeFile(String filename, object obj, bool deleteIfExists = true)
{
if (deleteIfExists)
{
FileManager.DeleteFile(filename);
}
Type[] extraTypes = ClassHandler.GetPropertiesTypes(obj, true);
using (var stream = new FileStream(filename, FileMode.Create))
{
//XmlSerializer xmlSerialize = new XmlSerializer(obj.GetType(), extraTypes);
XmlSerializer xmlSerialize = new XmlSerializer(obj.GetType(), GetXmlAttributeOverrides(obj.GetType()), extraTypes, null, null);
xmlSerialize.Serialize(stream, obj);
}
}
the two methods I use from my ClassHandler class:
public static T GetCustomAttribute<T>(this PropertyInfo propertyInfo, bool inherit) where T : Attribute
{
object[] attributes = propertyInfo.GetCustomAttributes(typeof(T), inherit);
return attributes == null || attributes.Length == 0 ? null : attributes[0] as T;
}
public static List<Type> GetImplementedInterfaces(Type type)
{
Type[] types = type.GetInterfaces();
List<Type> lTypes = new List<Type>();
foreach(Type t in types)
{
lTypes.Add(t);
}
return lTypes;
}
classes have the following structure:
interface IAnimal
{
// Properties
// Methods
}
public abstract class Animal : IAnimal
{
// Implements IAnimal properties and methods
// This XmlElement gets written correctly when XML Serializing
// Example:
[XmlElement("AnimalAge")]
public double Age
{
get { return _age; }
set { _age = value; }
}
}
public abstract class Bird : Animal, IAttributeWings
{
// Implements Attributes common for all "Birds"
// Setting "override" here gives me error
public bool HasWings { get { return _hasWings; } set { _hasWings = value; } }
}
public class Pelican : Bird, IAttributeCanFly
{
// Implements Attributes common for all "Pelicans"
// Does not implement previous attribute IAttributeWings since Bird class does this
// Setting "override" here gives me error as well
public bool CanFly { get { return _canFly; } set { _canFly = value; } }
}
and then the attribute interfaces only have properties like "bool CanFly, bool hasWings" and such and other attributes for a specific category like in this example.
Some reason it does not work in .NET... But I come up with this solution to overcome the problem you have using XmlAttributeOverrides. For more information about it, please visit the below link
http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlattributeoverrides.aspx
You can optimize it by refactoring / caching this GetXmlAttributeOverrides() method somewhere in your application. Hope it helps.
public interface ITest
{
[XmlAttribute("Name")]
string PropertyName { get; set; }
}
public class XTest : ITest
{
public string PropertyName
{
get;
set;
}
}
public class Program
{
static void Main(string[] args)
{
try
{
XTest xtest = new XTest() { PropertyName = "Raj" };
StringBuilder xmlString = new StringBuilder();
using (XmlWriter xtw = XmlTextWriter.Create(xmlString))
{
XmlSerializer serializer = new XmlSerializer(typeof(XTest), GetXmlAttributeOverrides(typeof(XTest)));
serializer.Serialize(xtw, xtest);
xtw.Flush();
}
Console.WriteLine( xmlString.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
public static XmlAttributeOverrides GetXmlAttributeOverrides(Type derivedType)
{
Type type = typeof(ITest);
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attr = new XmlAttributes();
foreach (PropertyInfo propertyInfo in type.GetProperties())
{
XmlAttributeAttribute xmlAttributeAttribute = propertyInfo.GetCustomAttribute(typeof(XmlAttributeAttribute), true) as XmlAttributeAttribute;
if (xmlAttributeAttribute == null) continue;
XmlAttributes attr1 = new XmlAttributes();
attr1.XmlAttribute = new XmlAttributeAttribute();
attr1.XmlAttribute.AttributeName = xmlAttributeAttribute.AttributeName;
overrides.Add(derivedType, propertyInfo.Name, attr1);
}
return overrides;
}
}
EDIT: Include this extension class in your application
public static class PropertyInfoEx
{
public static T GetCustomAttribute<T>(this PropertyInfo propertyInfo, bool inherit) where T : Attribute
{
object[] attributes = propertyInfo.GetCustomAttributes(typeof(T), inherit);
return attributes == null || attributes.Length == 0 ? null : attributes[0] as T;
}
}
Found some issues in your code. Below is the corrected code
public static XmlAttributeOverrides GetXmlAttributeOverrides(Type type)
{
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
// Get all interfaces that the "type" implements (is it same as "derivedType" from previously)?
foreach (Type derived in ClassHandler.GetImplementedInterfaces(type))
{
foreach (PropertyInfo propertyInfo in derived.GetProperties())
{
XmlAttributeAttribute xmlAttributeAttribute = ClassHandler.GetCustomAttribute<XmlAttributeAttribute>(propertyInfo, true) as XmlAttributeAttribute;
if (xmlAttributeAttribute == null) continue;
XmlAttributes attr1 = new XmlAttributes();
attr1.XmlAttribute = new XmlAttributeAttribute();
attr1.XmlAttribute.AttributeName = xmlAttributeAttribute.AttributeName;
overrides.Add(type, propertyInfo.Name, attr1);
}
}
return overrides;
}
Try for yourself. Let me know if it works.

Categories

Resources