How to use XmlSerializer to serialize derived instances? - c#

I realize this looks to be an exact duplicate of Using XmlSerializer to serialize derived classes, but I cannot figure out how to get this working following the guidance from that same question:
using System;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace xmlSerializerLab
{
public class Utf8StringWriter : System.IO.StringWriter
{
public override Encoding Encoding => Encoding.UTF8;
}
[XmlRoot(ElementName = "Query", Namespace = "http://www.opengis.net/wfs")]
public class Query
{
[XmlElement(ElementName = "Filter", Namespace = "http://www.opengis.net/ogc")]
public Filter Filter { get; set; }
}
[XmlInclude(typeof(PropertyIsOpFilter))]
[XmlInclude(typeof(PropertyIsEqualToFilter))]
[XmlInclude(typeof(OpFilterBase))]
[XmlInclude(typeof(LiteralFilter))]
[XmlInclude(typeof(Query))]
[Serializable]
public class Filter
{
[XmlElement]
public Filter And { get; set; }
}
public class PropertyIsOpFilter : Filter, IXmlSerializable
{
public Filter LeftOp { get; set; }
public Filter RightOp { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader) { }
public void WriteXml(XmlWriter writer)
{
Program.ToXml(LeftOp, writer);
Program.ToXml(RightOp, writer);
}
}
[XmlRoot("IsEqualTo")]
public class PropertyIsEqualToFilter : PropertyIsOpFilter { }
public class OpFilterBase : Filter, IXmlSerializable
{
public string Op { get; set; }
public object Value { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader) { }
public void WriteXml(XmlWriter writer)
{
if (!String.IsNullOrEmpty(Op))
{
writer.WriteStartElement(Op);
writer.WriteValue(Value);
writer.WriteEndElement();
}
else
{
writer.WriteValue(Value);
}
}
}
public class LiteralFilter : OpFilterBase { }
class Program
{
public static void ToXml(Object o, XmlWriter writer)
{
var inputSerializer = new XmlSerializer(o.GetType(), new Type[] {
typeof(Filter),
typeof(PropertyIsOpFilter),
typeof(PropertyIsEqualToFilter),
typeof(OpFilterBase),
typeof(LiteralFilter),
typeof(Query)
});
inputSerializer.Serialize(writer, o);
}
public static string ToXml(Object o)
{
var inputSerializer = new XmlSerializer(o.GetType());
using (var writer = new Utf8StringWriter())
{
using (var xmlWriter = new XmlTextWriter(writer))
{
ToXml(o, xmlWriter);
}
return writer.ToString();
}
}
static void Main(string[] args)
{
Filter o = new PropertyIsEqualToFilter()
{
LeftOp = new LiteralFilter()
{
Value = 1
},
RightOp = new LiteralFilter()
{
Value = 1
}
};
var query = new Query()
{
Filter = o
};
Console.WriteLine(ToXml(query));
Console.ReadLine();
}
}
}
It results in this exception:
InvalidOperationException: The type
xmlSerializerLab.PropertyIsEqualToFilter may not be used in this
context. To use xmlSerializerLab.PropertyIsEqualToFilter as a
parameter, return type, or member of a class or struct, the parameter,
return type, or member must be declared as type
xmlSerializerLab.PropertyIsEqualToFilter (it cannot be object).
Objects of type xmlSerializerLab.PropertyIsEqualToFilter may not be
used in un-typed collections, such as ArrayLists.
As far as I can tell, I need the IXmlSerializable on the PropertyIsOpFilter and OpFilterBase because I'm trying to target a specific XML format described by this schema. But I'm finding that I also have to make the Query class IXmlSerializable.
Here is a sample XML document that I'd like to be able to produce from the model:
<GetFeature xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
service="WFS"
version="1.1.0"
maxFeatures="0" xmlns="http://www.opengis.net/wfs">
<ResultType>Results</ResultType>
<OutputFormat>text/gml; subtype=gml/3.1.1</OutputFormat>
<Query
d2p1:srsName="EPSG:4326" xmlns:d2p1="http://www.opengis.net/ogc">
<d2p1:Filter>
<d2p1:IsEqualTo>
<d2p1:PropertyName>Prop1</d2p1:PropertyName>
<d2p1:Literal>1</d2p1:Literal>
</d2p1:IsEqualTo>
</d2p1:Filter>
</Query>
</GetFeature>
By making the Query class IXmlSerializable and writing a good bit of WriteXml and ReadXml logic I can get it to work, but I'd expect it to work without having to do all that since the XmlRoot and XmlAttribute and XmlElement tags should give enough information to the serializer for it to know which class to instantiate based on the tag name (match ElementName) and certainly how to serialize based on the attributes.

The problem you are seeing can be reproduced with the following minimal example:
public class BaseClass
{
}
public class DerivedClass : BaseClass, IXmlSerializable
{
#region IXmlSerializable Members
public XmlSchema GetSchema() { return null; }
public void ReadXml(XmlReader reader) { throw new NotImplementedException(); }
public void WriteXml(XmlWriter writer) { }
#endregion
}
Using the serialization code:
BaseClass baseClass = new DerivedClass();
using (var textWriter = new StringWriter())
{
using (var xmlWriter = XmlWriter.Create(textWriter))
{
var serializer = new XmlSerializer(typeof(BaseClass), new Type[] { typeof(DerivedClass) });
serializer.Serialize(xmlWriter, baseClass);
}
Console.WriteLine(textWriter.ToString());
}
The following exception is thrown (sample fiddle #1):
System.InvalidOperationException: There was an error generating the XML document.
---> System.InvalidOperationException: The type DerivedClass may not be used in this context. To use DerivedClass as a parameter, return type, or member of a class or struct, the parameter, return type, or member must be declared as type DerivedClass (it cannot be object). Objects of type DerivedClass may not be used in un-typed collections, such as ArrayLists.
This is among the most unhelpful exception messages I have seen from XmlSerializer. To understand the exception, you need to understand how XmlSerializer handles polymorphism via the [XmlInclude] mechanism. If I remove IXmlSerializable from DerivedClass, the following XML is generated (fiddle #2):
<BaseClass xsi:type="DerivedClass" />
Notice the xsi:type type attribute? That is a w3c standard attribute that XmlSerializer uses to explicitly assert the type of a polymorphic element; it is documented here. When XmlSerializer is deserializing a polymorphic type to which [XmlInclude] attributes have been applied (either statically or through the constructor you are using), it will look for the xsi:type attribute to determine the actual type to construct and deserialize.
It is this, apparently, which conflicts with IXmlSerializable. A type which implements this interface should completely control its XML reading and writing. However, by parsing and interpreting the xsi:type attribute, XmlSerializer has already begun automatic deserialization, and so throws an exception due to the inconsistent deserialization strategies of the base and derived types.
What's more, adding IXmlSerializable to the base type doesn't really fix the problem either If you do so, the xsi:type attribute is never written, and later, when ReadXml() is called, an object of the base type will get unconditionally constructed, as shown in fiddle #3.
(It's conceivable that Microsoft could have implemented a special case where XmlSerializer begins automatic deserialization, then "backs off" and hands the task over to ReadXml() when an IXmlSerializable polymorphic type is encountered and constructed. But, they did not.)
The solution would seem to be to serialize your Filter types automatically using the [XmlInclude] mechanism. In fact I don't see any reason you need to use IXmlSerializable, and was able to serialize your model successfully by removing IXmlSerializable completely and making some minor changes to namespaces:
public static class XmlNamespaces
{
public const string OpengisWfs = "http://www.opengis.net/wfs";
}
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class Query
{
public Filter Filter { get; set; }
}
[XmlInclude(typeof(PropertyIsOpFilter))]
[XmlInclude(typeof(OpFilterBase))]
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class Filter
{
[XmlElement]
public Filter And { get; set; }
}
[XmlInclude(typeof(PropertyIsEqualToFilter))]
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class PropertyIsOpFilter : Filter
{
public Filter LeftOp { get; set; }
public Filter RightOp { get; set; }
}
[XmlRoot("IsEqualTo", Namespace = XmlNamespaces.OpengisWfs)]
public class PropertyIsEqualToFilter : PropertyIsOpFilter { }
[XmlInclude(typeof(LiteralFilter))]
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class OpFilterBase : Filter
{
public string Op { get; set; }
public object Value { get; set; }
}
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class LiteralFilter : OpFilterBase { }
Notes:
For the [XmlInclude] mechanism to work, all the included types apparently must be in the same XML namespace as the base type. To ensure this I added [XmlRoot(Namespace = XmlNamespaces.OpengisWfs)] to all the Filter subtypes.
The [XmlInclude(typeof(DerivedType))] attributes can be added either to their immediate parent type or to the lowest common base type. In the code above I added the attributes to the immediate parent types so that members of an intermediate type could be serialized successfully, e.g.:
public class SomeClass
{
PropertyIsOpFilter IsOpFilter { get; set; }
}
Consider marking intermediate types that cannot be instantiated as abstract, e.g. public abstract class Filter. Consider marking types that are "most derived" as sealed, e.g. public sealed class LiteralFilter
If you use the new XmlSerializer(Type, Type []) constructor, you must statically cache the serializer to avoid a severe memory leak, as explained here. It's not necessary in my solution but you are using it in your question.
Sample fiddle #4 showing that the following XML is generated successfully:
<Query xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.opengis.net/wfs">
<Filter xsi:type="PropertyIsEqualToFilter">
<LeftOp xsi:type="LiteralFilter">
<Value xsi:type="xsd:int">1</Value>
</LeftOp>
<RightOp xsi:type="LiteralFilter">
<Value xsi:type="xsd:int">1</Value>
</RightOp>
</Filter>
</Query>

Related

XmlSerializer: deserialize recursive object graph

Given the following XML I want to deserialize:
<?xml version="1.0" encoding="utf-8" ?>
<units>
<entity>
<health max="1000"/>
<sprite texture="tank"/>
<entity>
<sprite texture="tank-turret"/> <!-- this element is missing when i deserialize --!>
</entity>
</entity>
</units>
How can I deserialize this recursive object graph using XmlSerializer?
The following is my last try. It successfully deserializes the top-level objects (health, sprite, entity) but it does not seem to find the sprite element in the nested entity node.
I also tried deriving entity from componentlist, but it didn't work either.
public class UnitSerializer
{
public abstract class item
{
}
public class entity : item
{
[XmlArray("entity")]
[XmlArrayItem(typeof(health))]
[XmlArrayItem(typeof(entity))]
[XmlArrayItem(typeof(sprite))]
public componentlist entity2 { get; set; }
}
public abstract class component : item
{
}
public class health : component
{
[XmlAttribute]
public int max { get; set; }
}
public class sprite : component
{
[XmlAttribute]
public string texture { get; set; }
}
public class componentlist : List<item>
{
}
[XmlRoot("units")]
public class units
{
[XmlArray("entity")]
[XmlArrayItem(typeof(health))]
[XmlArrayItem(typeof(entity))]
[XmlArrayItem(typeof(sprite))]
public componentlist entity { get; set; }
}
public void Read()
{
var x = new XmlSerializer(typeof(units),
new[] {
typeof(componentlist),
typeof(entity),
typeof(health),
typeof(sprite)
});
var fs = new FileStream("units.xml", FileMode.Open);
XmlReader reader = new XmlTextReader(fs);
var units = (units)x.Deserialize(reader);
}
}
Your classes can be fixed by replacing use of [XmlArray] and [XmlArrayItem] with [XmlElement(typeof(TDerived))]:
public class UnitSerializer
{
public abstract class item
{
}
public class entity : item
{
[XmlElement("health", typeof(health))]
[XmlElement("entity", typeof(entity))]
[XmlElement("sprite", typeof(sprite))]
public List<item> EntityList { get; set; }
}
public abstract class component : item
{
}
public class health : component
{
[XmlAttribute]
public int max { get; set; }
}
public class sprite : component
{
[XmlAttribute]
public string texture { get; set; }
}
[XmlRoot("units")]
public class units
{
[XmlElement("health", typeof(health))]
[XmlElement("entity", typeof(entity))]
[XmlElement("sprite", typeof(sprite))]
public List<item> EntityList { get; set; }
}
public units Read(string filename)
{
var x = new XmlSerializer(typeof(units));
using (var fs = new FileStream(filename, FileMode.Open))
using (var reader = XmlReader.Create(fs))
{
return (units)x.Deserialize(reader);
}
}
}
Notes:
[XmlArray] indicates a collection should be serialized with an outer wrapper element containing a sequence of elements, while [XmlElement] indicates a collection should be serialized as a sequence without the wrapper. Your XML sample uses repeating elements without wrapper elements, so [XmlElement] should be used. It sort of works because your XML is recursive -- but at every other level repeating elements are getting incorrectly deserialized as wrappers. This explains why some but not all data is lost during deserialization.
In your XML sample, polymorphic elements are identified by element name. The XmlSerializer(Type, Type[]) constructor should be used to specify polymorphic included types to be serialized using the xsi:type mechanism. Since the xsi:type attribute does not appear in your XML, this constructor need not be used.
(In addition, when constructing an XmlSerializer using the XmlSerializer(Type, Type[]) constructor, you must cache the serializer statically to avoid a severe memory leak. See Memory Leak using StreamReader and XmlSerializer for why.)
XmlTextReader has been deprecated since .Net 2.0. Use XmlReader.Create() instead.
The FileStream and XmlReader should be disposed, ideally via using statements.
I eliminated the public class componentlist : List<item> and replaced it with just a List<item>. This was mainly a matter of taste, but it does make it easier to set the value of such a list using Linq's .ToList().
Demo fiddle here.

Using C# abstract class property for Xml serialize

I want to create an generic abstract type as base for my xml serialized types:
public abstract class RootElementBase<TEelment>
{
public IList<TElement> SubElements {get;set;}
public RootElementBase(){
SubElements = new List<T>();
}
}
And I will use like this:
[XmlRoot(ElementName = "myroot")]
public class MyRoot: RootElementBase<ItemType> {
[XmlElement("item")]
public override List<ItemType> Elements { get; set; }
}
But this does not serialize MyRoot class. Implemented types use generic abstract class for Elements type. But XmlElelemt attribute tags will be set.
Serialization and deserialization of derived types is supported. The following attributes control the Xml Serialization:
[XmlElement]
[XmlAttribute]
[XmlIgnore]
We need to instruct the Xml serializer to ignore the base class members that we wish to work with in our concrete derived types.
See -> https://learn.microsoft.com/en-us/dotnet/standard/serialization/attributes-that-control-xml-serialization
Also, be-careful with virtual member calls in the constructor
See -> https://msdn.microsoft.com/en-us/library/ms182331.aspx
Give this a shot:
Solution 1
Using the derived class MyRoot as the type parameter for the XmlSerializer
Abstract base class:
[Serializable]
public abstract class RootElementBase<TEelment>
{
[XmlIgnore]
public virtual List<TEelment> SubElements { get; set; }
protected RootElementBase()
{
SubElements = new List<TEelment>();
}
}
Concrete Class:
[XmlRoot(ElementName = "myroot")]
public class MyRoot : RootElementBase<ItemType>
{
[XmlElement("item")]
public override List<ItemType> SubElements { get; set; }
}
Dummy ItemType Class:
public class ItemType
{
public string Name { get; set; }
}
This will output the following:
<?xml version="1.0"?>
<myroot xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<item>
<Name>Jim</Name>
</item>
<item>
<Name>Ben</Name>
</item>
<item>
<Name>Tom</Name>
</item>
</myroot>
Test Console App:
class Program
{
static void Main(string[] args)
{
MyRoot root = new MyRoot();
root.SubElements.Add(new ItemType() { Name = "Jim"});
root.SubElements.Add(new ItemType() { Name = "Ben" });
root.SubElements.Add(new ItemType() { Name = "Tom" });
XmlSerializer xmlSerializer = new XmlSerializer(typeof(MyRoot));
StringWriter stringWriter = new StringWriter();
xmlSerializer.Serialize(stringWriter, root);
Console.WriteLine(stringWriter);
Console.ReadKey();
}
}
Solution 2
Using the abstract base class RootElementBase as the type parameter for the XmlSerializer with an XmlRoot override parameter
Per MSDN:
The root element of an XML document encloses all the other elements.
By default, the object specified by the type parameter is serialized
as the root element. Properties, such as the XML element name of the
root element are taken from the type object. However, the root
parameter allows you to replace the default object's information by
specifying an XmlRootAttribute; the object allows you to set a
different namespace, element name, and so on.
https://msdn.microsoft.com/en-us/library/65k4wece(v=vs.110).aspx
Abstract Base Class:
[Serializable]
[XmlInclude(typeof(MyRoot))]
public abstract class RootElementBase<TEelment>
{
[XmlIgnore]
public virtual List<TEelment> SubElements { get; set; }
protected RootElementBase()
{
SubElements = new List<TEelment>();
}
}
Concrete Class:
[XmlRoot(ElementName = "myroot")]
public class MyRoot : RootElementBase<ItemType>
{
[XmlElement("item")]
public override List<ItemType> SubElements { get; set; }
}
Dummy ItemType Class:
public class ItemType
{
public string Name { get; set; }
}
Sample Console App with Generic Serialization
class Program
{
static void Main(string[] args)
{
MyRoot root = new MyRoot();
root.SubElements.Add(new ItemType() { Name = "Jim"});
root.SubElements.Add(new ItemType() { Name = "Ben" });
root.SubElements.Add(new ItemType() { Name = "Tom" });
string xml = Serialize(root, "myNewRoot");
Console.WriteLine(xml);
Console.ReadKey();
}
static string Serialize<TElement>(RootElementBase<TElement> tElement, string rootElementName)
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(RootElementBase<TElement>),
new XmlRootAttribute(rootElementName));
StringWriter stringWriter = new StringWriter();
xmlSerializer.Serialize(stringWriter, tElement);
return stringWriter.ToString();
}
}
Note - With This solution you need to be aware of Dynamically Generated Assemblies
Dynamically Generated Assemblies
To increase performance, the XML serialization infrastructure dynamically generates assemblies to serialize and deserialize specified types. The infrastructure finds and reuses those assemblies. This behavior occurs only when using the following constructors:
XmlSerializer.XmlSerializer(Type)
XmlSerializer.XmlSerializer(Type, String)
If you use any of the other constructors, multiple versions of the same assembly are generated and never unloaded, which results in a memory leak and poor performance. The easiest solution is to use one of the previously mentioned two constructors. Otherwise, you must cache the assemblies in a Hashtable, as shown in the following example.
Please see MSDN for Remarks: https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer(v=vs.110).aspx

How to implement ReadXml for SomeClass : IList<IFoo> where all instances of IFoo are IFoo<T> of varying T

I have a class that I need to serialize/deserialize, and I'm half way there - I have serialization functional, resulting in the below XML. However, since I'm implementing IXmlSerializable myself, I'm uncertain what an implementation of ReadXml should look like, given that SomeGenericClass<T> was serialized using attribute-based flagging rather than an explicit implementation if IXmlSerializable
<?xml version="1.0" encoding="utf-16"?>
<FooContainer FooName="DoSomething">
<SomeGenericClassOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Value="Foobar" Name="firstParam" Description="First Paramater Serialized" />
<SomeGenericClassOfInt32 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Value="10000" Name="nextParam" Description="Second Serialized parameter" />
</FooContainer>
Which I want to serialize back into an instance of:
public class FooContainer : IList<ISomeGenericClassBase>, IXmlSerializable
{
public string FooName {get;set;}
void IXmlSerializable.WriteXml(XmlWriter writer) {
var serializer = XmlSerializer.FromTypes(new Type[]{SomeGenericBaseClass})[0];
this
.Select(item=>SomeGenericClassBase.ConvertToMe(item))
.ToList()
.ForEach(item=>serializer.Serialize(writer, item));
}
// IList Implementation omitted - wraps a private List<ISomeGenericClassBase>
}
Where the list will contain instances along these lines:
public interface ISomeGenericClassBase
{
}
public interface ISomeGenericBaseClass<T> : ISomeGenericBaseClass
{
}
public class SomeGenericClassBase : ISomeGenericClassBase
{
public static SomeGenericClassBase ConvertToMe(ISomeGenericClassBase target) {
return new SomeGenericClassBase() {Property1 = target.Property1; Property2 = target.Property2}
}
public static ISomeGenericBaseClass ExpantToTyped(SomeGenericClassBase target) {
// Implementation omitted - converts a base class instance to a generic instance by working out the generic type from saved data and reconstructing
}
}
public class SomeGenericClass<T> : SomeGenericClassBase, ISomeGenericBaseClass<T>
{
[XmlAttribute]
public string Name {get;set;}
[XmlAttribute]
public string Description{get;set;}
[XmlAttribute]
public T Value {get;set;}
[XmlElement("")]
public T[] ValidOptions {get;set;}
}
EDIT: Expanded the implementation - realised as it was, it didn't illustrate the problem correctly
Core issue is that I want to be able to serialize items that only implement the interface, even if I only get back SomeGenericClassBase instances. Per the approach used in the ExpandToTyped method, I'm expecting consumers of the class to save sufficient data in their implementations that allow the resulting classes to be converted back into their original form as required. So yes, there's a loss of fidelity, but it's one I can live with in exchange for the flexibility of using a list of interfaces instead of a list of base classes.
One solution is to sidestep the issue (IXmlSerializable.ReadXml looks quite painful anyway, e.g. for collections). What I eventually did is scrap IXmlSerializable, and instead generate a class along the lines of the below.
Please note that whilst this approach works, it's currently quite error prone if the serializable instance is used for anything other than serialization - synchronization is maintained ONLY when SerializationTarget is set or retrieved. When it's set, we convert existing parameters to appropriate instances and add them to a serializable list. When it's retrieved, if it's null, we inflate from whatever was in the current value.
However, if FooContainer changes after the creation of this object, it won't maintain that synchronization and what gets serialized will be out of date. This is largely because I'm lazy and don't want to implement IList<SomeGenericClassBase> again to override the Add and Remove methods (though this would be the more robust approach).
public class FooContainerSerializable
{
public FooContainerSerializable() {}
public FooContainerSerializable(FooContainer serializationTarget)
{
this.SerializationTarget = serializationTarget;
}
[XmlIgnore]
public FooContainer SerializationTarget
{
get {
if (_SerializationTarget == null)
{
_SerializationTarget = new FooContainer();
// Copy across extant collection properties here
this.Parameters.ForEach(item=>_SerializationTarget.Add(item));
}
return _SerializationTarget;
}
set {
// Synchronize this entity's entries here
_SerializationTarget = value;
_SerializationTarget.ForEach(item=>this.Parameters.Add(item.Deflate()));
}
}
private FooContainer _SerializationTarget;
[XmlElement]
public string FooName {
get {return this.SerializationTarget.FooName;}
set {this.SerializationTarget.FooName = value;}
}
[XmlElement]
public List<SomeGenericClassBase> Parameters {
get {return _Parameters ?? (_Parameters = new List<SomeGenericClassBase>());}
set {_Parameters = value;}
}
}
Here is another option if you are willing to use an abstract class instead of an interface in your collection definition. You'd also need to declare all the derived types of SomeGenericClassBase using XmlInclude attributes. I'm thinking this wouldn't be too bad if there are just a handful of types you'd use with this class.
[XmlRoot(ElementName = "FooContainer")]
public class FooContainer : List<SomeGenericClassBase>
{
[XmlAttribute]
public string FooName { get; set; }
}
[XmlInclude(typeof(SomeGenericClass<string>))]
[XmlInclude(typeof(SomeGenericClass<int>))]
public abstract class SomeGenericClassBase
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Description { get; set; }
}
public class SomeGenericClass<T> : SomeGenericClassBase
{
[XmlAttribute]
public T Value { get; set; }
[XmlElement]
public T[] ValidOptions { get; set; }
}
class Class1
{
public static void Run()
{
var f = new FooContainer()
{
new SomeGenericClass<string> { Name = "firstParam", Description = "First Paramater Serialized", Value = "Foobar"},
new SomeGenericClass<int> { Name = "nextParam", Description = "Second Serialized parameter", Value = 10000 }
};
f.FooName = "DoSomething";
XmlSerializer serializer = new XmlSerializer(f.GetType());
StringBuilder sb = new StringBuilder();
// Serialize
using (StringWriter writer = new StringWriter(sb))
{
serializer.Serialize(writer, f);
}
Console.WriteLine(sb);
// Deserialize
using(StringReader reader = new StringReader(sb.ToString()))
{
FooContainer f2 = (FooContainer)serializer.Deserialize(reader);
}
}
}
This would serialize to the following XML:
<?xml version="1.0" encoding="utf-16"?>
<FooContainer xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SomeGenericClassBase xsi:type="SomeGenericClassOfString" Name="firstParam" Description="First Paramater Serialized" Value="Foobar" />
<SomeGenericClassBase xsi:type="SomeGenericClassOfInt32" Name="nextParam" Description="Second Serialized parameter" Value="10000" />
</FooContainer>
Deserialization maintains full fidelity.

Serialization of class attributes?

Is the default XmlSerializer capable of serializing class attributes as Xml attributes?
[MyClassTypeAttribute(ClassType.MyClass)]
public MyClass : BaseClass {
}
would turn to
<myclass MyClassType="MyClass">
Reason:
I have a WCF service that sends me different objects through the same operation contract which all derive from BaseClass. To know which type of object it is and to cast it directly (and serialize it as Xml to write in a document afterwards), I'd like to have some 'type' attribute (enum).
One possibility is, of course, declaring a property as XmlAttribute
[XmlAttribute(params)]
public MyClassType { get; set; }
Problem here is: The XmlSerializer (DataContractSerializer as well, AFAIK) forces me to have a setter on every property. I know I can declare the setter as protected and it still works (XmlSerializer, you naughty little thing), but don't really like that solution because 1) I think there is a reason that I'm able to leave out the setter in POCOs usually and 2) declaring some properties as XmlAttributes and others as XmlElements is confusing (it's like putting dogs and cats into a cat goulash.
(Additionally, is it possible to force a derived class to declare certain attributes?)
[abstract MyClassTypeAttribute]
if it is about the type of your class here is an example:
[XmlIncludeAttribute(typeof(ConcreteFooOne))]
[XmlIncludeAttribute(typeof(ConcreteFooTwo))]
[XmlIncludeAttribute(typeof(ConcreteFooThree))]
[XmlRoot(ElementName = "FooData", Namespace = "http://foo.bar")]
public abstract partial class AbstractFoo
{
// Some abstract props etc.
}
[XmlRoot(ElementName = "FooData", Namespace = "http://foo.bar")]
public class ConcreteFooOne : AbstractFoo
{
public int MyProp { get; set; }
}
[XmlRoot(ElementName = "FooData", Namespace = "http://foo.bar")]
public class ConcreteFooTwo : AbstractFoo
{
}
[XmlRoot(ElementName = "FooData", Namespace = "http://foo.bar")]
public class ConcreteFooThree : AbstractFoo
{
}
class Program
{
static void Main(string[] args)
{
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(AbstractFoo));
using (var stream = new FileStream("test.txt", FileMode.OpenOrCreate))
{
serializer.Serialize(stream, new ConcreteFooOne() { MyProp = 10 });
stream.Flush();
}
using (var stream = new FileStream("test.txt", FileMode.OpenOrCreate))
{
var c = serializer.Deserialize(stream);
}
}
}
The code will serialize and include the type attribute and when you deserialize you will get the right instance.

Using IXmlSerialization, how can I serialize a T property?

My question is so simple.
Imagine we have a Foo class. and has a T property.
public class Foo<T>
{
public T Property { get; set; }
}
I want to implement in it IXmlSerializable and my problem is I have no idea to imitate the standard XmlSerialization. I really have no idea about how to write this property like the standart XmlSerlalization.
UPDATE: I did this and it's not working
public class Foo<T> : IFoo
where T : IXmlSerializable
{
public T Value { get; set; }
}
public class FooA : Foo<string> { }
public class FooB : Foo<int> { }
public class FooC : Foo<List<Double>> { }
When I serialized this classes (A, B and C). I wanna have something like this:
Using first class:
<FooA>
<Value>StringDemo</Value>
</FooA>
Using second class:
<FooB>
<Value>2</Value>
</FooB>
Using third class:
<FooC>
<Value>
<ArrayOfDouble xsi:..>
<Double>3.1416</Double>
<Double>4.2</Double>
</ArrayOfDouble>
</Value>
</FooC>
Something like this is what I wanted, I don't want to implement in all of this a custom XmlSerializer, I mean the default which the standard serialization use.
You would need to implement IXmlSerializable in Foo<T>, and T (using where T : IXmlSerializable. The implementation in Foo<T> would then be responsible for writing a wrapper element with type information (in WriteXml) and call WriteXml on T, and recreate the instance from the type information (in ReadXml) and call ReadXml on T.
Basic types like String and Double is not IXmlSerializable, so you cant have where T : IXmlSerializable if you want to support them. You can also provide default implementations of ReadXml and WriteXml. This works since you need to create a Foo<T> before calling ReadXml, forcing you to know T.
public class Foo<T> : IXmlSerializable {
public T Value { get; set; }
XmlSchema IXmlSerializable.GetSchema() {
return GetSchema();
}
protected virtual XmlSchema GetSchema() {
return null;
}
public virtual void ReadXml(XmlReader reader) {
reader.ReadStartElement();
var xmlSerializer = new XmlSerializer(typeof(T));
Value = (T)xmlSerializer.Deserialize(reader);
reader.ReadEndElement();
}
public virtual void WriteXml(XmlWriter writer) {
var xmlSerializer = new XmlSerializer(typeof(T));
xmlSerializer.Serialize(writer, Value);
}
}

Categories

Resources