Serializing XML with multiple possible inner elements - c#

I'm having trouble figuring out how to construct and configure a class structure for serializing/deserializing XML such as this in .Net:
<OuterElem>
<InnerElem>
<C1>...</C1>
</InnerElem>
</OuterElem>
Some notes:
C1 is a complex type.
There can also be C2, C3 etc. distinct complex types.
There can be one or multiple of the Cx elements present at the same time.
OuterElem and InnerElem comes from a different namespace than the Cx elements.
At runtime (actually compiletime), the complete set of possible Cx elements is well known the current use case, however the wrapping elements are part of a generic solution so preferably they should not depend on or know about the Cx elements.
This is what I have so far:
[XmlRoot(Namespace = "urn:outer-ns")]
public class OuterElem
{
public object[] InnerElem { get; set; }
}
[XmlRoot(ElementName = "C1", Namespace = "urn:ns1")]
public class C1 { }
[XmlRoot(ElementName = "C2", Namespace = "urn:ns1")]
public class C2 { }
But this doesn't give what I want. Serializing with:
var xs = new XmlSerializer (typeof(OuterElem),
new [] {typeof(C1), typeof(C2)});
var s = new StringWriter();
xs.Serialize (s, outerElemInstance);
yields XML as:
<?xml version="1.0" encoding="utf-16"?>
<OuterElem>
<Body>
<anyType xmlns:q1="urn:ns1" xsi:type="q1:C1">
How can I make it use the element name C1 instead of this anyType?

For this use case, typically the InnerElem has to be decorated with the XmlElement attribute as below:
public class OuterElem
{
[XmlElement(typeof(C1))]
[XmlElement(typeof(C2))]
public object[] InnerElem { get; set; }
}
However as you have mentioned that you are working on a generic solution which shall not depend on the C1/C2... types, one possible solution is to add these attributes through XmlAttributeOverrides
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attribs = new XmlAttributes();
attribs.XmlIgnore = false;
attribs.XmlElements.Add(new XmlElementAttribute(typeof(C1)));
attribs.XmlElements.Add(new XmlElementAttribute(typeof(C2)));
overrides.Add(typeof(OuterElem), "InnerElem", attribs);
var xs = new XmlSerializer(typeof(OuterElem), overrides, new [] {typeof(C1), typeof(C2)}, null, null);
var s = new StringWriter();
xs.Serialize(s, outerElemInstance);

Related

How to serialize a custom object with a list of another custom object in a particular schema when this list of custom objects is a generic type

I need to create a lot of XML files. They are very similar and they need to have the following schema:
<Servidor>
<VersaoLayout>0001</VersaoLayout> <!--Will not change-->
<CodigoUJ>001001</CodigoUJ> <!--Will not change-->
<ItemServidor> <!--A list of <T> that will change-->
<CPFServidor>13579024681</CPFServidor>
<NomeServidor>Fulano Pereira Tal</NomeServidor>
</ItemServidor>
</Servidor>
Note that the tags <VersaoLayout> and <CodigoUJ> are the same for all the files i need to create, the <ItemServidor> is the content that will vary. So i thought: Why not create just one class and use generics to handle the differences for this case? And for now my classes are the following:
//Kind of a container class that will hold the content that will vary.
[XmlRoot("Servidor", ElementName="Servidor")]
public class LayoutArquivoXML<T> where T : ItemLayout
{
[XmlElement("VersaoLayout")]
public string VersaoLayout { get; set; }
[XmlElement("CodigoUJ")]
public string CodigoUJ { get; set; }
//[XmlArrayItem("ItemServidor")]
public List<T> ItensLayout { get; set; }
// Constructors omited for simplicity
}
//A "ContentClass". I will have a bunch of classes similar to this
[XmlRoot("ItemServidor", ElementName = "ItemServidor")]
public class ServidorLayout : ItemLayout
{
[XmlElement("CPFServidor")]
public string CPFServidor { get; set; }
[XmlElement("NomeServidor")]
public string Nome { get; set; }
}
I am instantiating my "container class" this way:
LayoutArquivoXML<ServidorLayout> layout = new Layout.LayoutArquivoXML<ServidorLayout>("versao01", "999", lstItensLayout.ToList());
And my Serialization method is the following:
public string Serializador<T>(T objeto)
{
string xml = string.Empty;
using (var sw = new ISO8859StringWriter())
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", ""); // to omit XML namespaces
var xs = new XmlSerializer(typeof(T));
xs.Serialize(sw, objeto, ns);
}
return xml;
}
These objects graph are generating a XML with the following schema:
<Servidor>
<VersaoLayout>versao01</VersaoLayout>
<CodigoUJ>999</CodigoUJ>
<ItensLayout>
<ServidorLayout>
<CPFServidor>4252813450</CPFServidor>
<NomeServidor>Antonio de Sousa Muniz</NomeServidor>
</ServidorLayout>
</ItensLayout>
</Serv>
I dont want the tag <ItensLayout> tag in the XML. What i need to do to generate the XML in the desired schema?
Also, the root tag <Servidor> will need to change according to the file i am generating, imagine that i need to create another file where the root tag is Another, let say:
LayoutArquivoXML<AnotherLayout> layout = new Layout.LayoutArquivoXML<AnotherLayout>("versao01", "999", anotherItensLayout.ToList());
There are two separate issues.
I don't want the tag <ItensLayout> tag in the XML. What i need to do to generate the XML in the desired schema?
As explained in the documentation for Serializing an Array as a Sequence of Elements, to serialize an array (list, enumerable etc.) as flat sequence of XML elements, you need to apply XmlElement attribute to the property/field.
Also, the root tag <Servidor> will need to change according to the file i am generating
This is controlled by the XmlRoot attribute.
Now, all this applies if you are using concrete classes. In order to apply them dynamically to your generic class, you can use the XmlOverrides class and the XmlSerializer constructors accepting such parameter.
To do that, first add an optional parameter to your original method:
public string Serializador<T>(T objeto, XmlAttributeOverrides overrides = null)
{
string xml = string.Empty;
using (var sw = new ISO8859StringWriter())
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", ""); // to omit XML namespaces
var xs = new XmlSerializer(typeof(T), overrides);
xs.Serialize(sw, objeto, ns);
xml = sw.ToString();
}
return xml;
}
Then create a specific method like this:
public string Serializador<T>(LayoutArquivoXML<T> objeto, string rootElementName, string contentElementName)
where T : ItemLayout
{
var xmlAttrOverrides = new XmlAttributeOverrides();
// Root element name override
var xmlRootAttrs = new XmlAttributes();
xmlRootAttrs.XmlRoot = new XmlRootAttribute(rootElementName);
xmlAttrOverrides.Add(typeof(LayoutArquivoXML<T>), xmlRootAttrs);
// Content element name override
var xmlContentAttrs = new XmlAttributes();
xmlContentAttrs.XmlElements.Add(new XmlElementAttribute(contentElementName));
xmlAttrOverrides.Add(typeof(LayoutArquivoXML<T>), "ItensLayout", xmlContentAttrs);
// Call the original method passing the overrides
return Serializador(objeto, xmlAttrOverrides);
}
Now you can achieve your goal by using something like this:
var source = new LayoutArquivoXML<ServidorLayout>
{
VersaoLayout = "0001",
CodigoUJ = "001001",
ItensLayout = new List<ServidorLayout>
{
new ServidorLayout
{
CPFServidor = "13579024681",
Nome = "Fulano Pereira Tal",
},
}
};
var xml = Serializador(source, "Servidor", "ItemServidor");
which produces:
<Servidor>
<VersaoLayout>0001</VersaoLayout>
<CodigoUJ>001001</CodigoUJ>
<ItemServidor>
<CPFServidor>13579024681</CPFServidor>
<NomeServidor>Fulano Pereira Tal</NomeServidor>
</ItemServidor>
</Servidor>

Using XmlAttributeOverrides on Nested Properties

I am trying to use XmlAttributeOverrides to control which class properties appear in the xml after the class has been serialized. It works on properties that are on the "root" class but not on nested properties. Here is a simple example to illustrate what I'm trying to accomplish.
My class hierarchy is as follows:
public class Main
{
public string Name { get; set; }
public Location Address { get; set; }
}
public class Location
{
public string StreetAddress { get; set; }
public Contact ContactInfo{ get; set; }
}
public class Contact
{
public string PhoneNumber { get; set; }
public string EmailAddr { get; set; }
}
When I serialize Main(), I get something like this:
<Main>
<Name></Name>
<Address>
<StreetAddress></StreetAddress>
<ContactInfo>
<PhoneNumber></PhoneNumber>
<EmailAddr></EmailAddr>
</ContactInfo>
</Address>
</Main>
What I am able to do is keep either Name or Address from appearing by using this:
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attribs = new XmlAttributes();
attribs.XmlIgnore = true;
attribs.XmlElements.Add(new XmlElementAttribute("Address"));
overrides.Add(typeof(Main), "Address", attribs);
xs = new XmlSerializer(typeof(Main), overrides);
What I need to also be able to do is keep Main.Address.ContactInfo from being serialized SOMETIMES (if it's empty). I tried the following but they didn't work:
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attribs = new XmlAttributes();
attribs.XmlIgnore = true;
attribs.XmlElements.Add(new XmlElementAttribute("ContactInfo "));
overrides.Add(typeof(Contact), "ContactInfo ", attribs);
xs = new XmlSerializer(typeof(Contact), overrides);
and...
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attribs = new XmlAttributes();
attribs.XmlIgnore = true;
attribs.XmlElements.Add(new XmlElementAttribute("ContactInfo "));
overrides.Add(typeof(Main.Address.ContactInfo), "ContactInfo ", attribs);
xs = new XmlSerializer(typeof(Main.Address.ContactInfo), overrides);
I've actually tried a lot more, including using XPath statements to designate the attribute name to target but didn't want to fill this page up with failed attempts. Is what I'm asking even possible by this method?
For anyone else trying to do this with XmlAttributeOverrides, turns out that #user1437872 was very close to finding the answer. Here is the override code to ignore the nested element ContactInfo.
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attribs = new XmlAttributes();
attribs.XmlIgnore = true;
attribs.XmlElements.Add(new XmlElementAttribute("ContactInfo"));
overrides.Add(typeof(Address), "ContactInfo ", attribs);
xs = new XmlSerializer(typeof(Main), overrides);
There are easier ways to achieve what you're looking for.
You said that what you are trying to achieve is to not serialize /Main/Address/ContactInfo if ContactInfo contains no data.
If you leave your code as is, it will serialize all of Main's properties, whether they are null or empty or not. The first step, is you need to add a XmlSerializerNamespaces property to all of your objects or each empty object will be serialized as <myElement xsi:nil="true" />. This can be accomplished easily, as follows:
public MyXmlElement
{
public MyXmlElement()
{
// Add your own default namespace to your type to prevet xsi:* and xsd:*
// attributes from being generated.
this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
new XmlQualifiedName(string.Empty, "urn:myDefaultNamespace") });
}
[XmlElement("MyNullableProperty", IsNullable=false)]
public string MyNullableProperty
{
get
{
return string.IsNullOrWhiteSpace(this._myNullableProperty) ?
null : this._myNullableProperty;
}
set { this._myNullableProperty = value; }
}
[XmlNamespacesDeclaration]
public XmlSerializerNamespaces Namespaces { get { return this._namespaces; } }
private XmlSerializerNamespaces _namespaces;
}
The code above declares a Namespaces property that holds all the relevant namespaces for the XML object. You should provide a default namespace for all of your objects (modeled after the code above). This prevents the xsi:* and xsd:* attributes from being output for your objects when they are serialized. Also, specify that the element is not nullable by using the System.Xml.Serialization.XmlElementAttribute.
Furthermore, by checking for string.IsNullOrWhiteSpace(someVariable) and returning null, then the
property will not be serialized when the above has been done.
So, putting this all together for your Location class:
public class Location
{
// You should have a public default constructor on all types for (de)sereialization.
public Location()
{
this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
new XmlQualifiedName(string.Empty, "urn:myNamespace"); // Default namespace -- prevents xsi:nil="true" from being generated, as well as xsd:* attributes.
});
}
public string StreetAddress
{
// If you don't want <StreetAddress xsi:nil="true" /> to be generated, do this:
get { return string.IsNullOrEmpty(this._streetAddress) ? null : this._streetAddress; }
// Otherwise, if you don't care, just do
// get;
// Only need to implement setter if you don't want xsi:nil="true" to be generated.
set { this._streetAddress = value; }
// Otherwise, just
// set;
}
private string _streetAddress;
[XmlElement("ContactInfo", IsNullable=false)]
public Contact ContactInfo
{
// You must definitely do this to prevent the output of ContactInfo element
// when it's null (i.e. contains no data)
get
{
if (this._contactInfo != null && string.IsNullOrWhiteSpace(this._contactInfo.PhoneNumber) && string.IsNullOrWhiteSpace(this._contactInfo.EmailAddr))
return null;
return this._contactInfo;
}
set { this._contactInfo = value; }
}
private Contact _contactInfo;
[XmlNamespacesDeclarations]
public XmlSerializerNamespaces Namespaces
{
get { return this._namespaces; }
}
private XmlSerializerNamespaces _namespaces;
}
With these changes to your Location class, the empty ContactInfo property should no longer be serialized to XML when none of the properties are null, empty, or whitespace, or if ContactInfo itself is null.
You should make similar changes to your other objects.
See my other stackoverflow answers for more on .NET XML serialization:
XmlSerializer: remove unnecessary xsi and xsd namespaces
Omitting all xsi and xsd namespaces when serializing an object in .NET?
Suppress xsi:nil but still show Empty Element when Serializing in .Net
There is no need to add a ContactInfo element to the attribs
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
XmlAttributes attribs = new XmlAttributes();
attribs.XmlIgnore = true;
overrides.Add(typeof(Address), "ContactInfo ", attribs);
xs = new XmlSerializer(typeof(Main), overrides);

Can I dynamically apply XmlIgnore to properties of an ArrayItem?

I am trying to serialize a List<T> where T: EntityObject and would love to leave out all the EntityKey and other EntityReference properties from the items in the list. Can this be done dynamically possibly using XmlAttributeOverrides?
As far as I can see, the XmlAttributeOverrides options only really point to the top level object ie the List<T> and not the T themselves, which is not very helpful to me.
Could anyone point me to a way to dynamically ignore properties of ArrayItems?
Here is a simple example I have been using that does not use EntityObjects but it should illustrate what I would like to do:
public class Car
{
public String Make { get; set; }
public String Model { get; set; }
public Double EngineSize { get; set; }
}
[Test]
public void WouldLoveToDynamicallyLeaveOutMembersOfArrayItems()
{
var cars = new List<Car>
{
new Car
{
Make = "Ferrari",
Model = "F1",
EngineSize = 6000
},
new Car
{
Make = "Williams",
Model = "F1",
EngineSize = 5500
}
};
var attributeOverrides = new XmlAttributeOverrides();
attributeOverrides.Add(typeof(Double), "EngineSize", new XmlAttributes {XmlIgnore = true});
var xs = new XmlSerializer(cars.GetType(), attributeOverrides, new []{ typeof(Car) }, new XmlRootAttribute("cars"), "");
var ms = new MemoryStream();
xs.Serialize(ms, cars);
ms.Position = 0;
var sr = new StreamReader(ms);
var result = sr.ReadToEnd();
Assert.IsFalse(result.Contains("EngineSize"));
}
Yes you can do that - the main error is you need typeof(Car) instead of typeof(double). With this, note that XmlAttributeOverrides does not just apply to the top-level onject.
However, I'm not sure that is the easiest route. Firstly, note that you must store and re-use the serialiser when using XmlAttributeOverrides otherwise you will leak assemblies.
I expect the main reason you want to do this is because you don't want to edit he generated file. However, there is another way; in a separate file, in the right namespace, you can use:
partial class Car {
public bool ShouldSerializeEngineSize() { return false; }
}
Where "ShouldSerialize*" is a pattern recognised and used by XmlSerializer to control conditional serialization. The "partial" class is simply a way of combining two separate code files into a single type (and was designed for this generated-content scenario).
This way, you dont need to mess with XmlAttributeOverrides, and you can use the simpler "new XmlSeralizer(Type)" (which has automatic caching per-type).

C# xmlserialization storing one class in two different way - possible?

is it possible to use C# XmlSerialization API to store single class in two different ways ?
For example
class Test
{
int group1_attr1;
int group1_attr2;
int group2_attr1;
}
I would like to have a way how to split class fields into two parts(groups - with specified attributes) and each time I call Serialize to control which part will be stored. For example if saving as group1
<?xml version="1.0" encoding="utf-8"?>
<Test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org /2001/XMLSchema">
<group1_attr1>0</group1_attr1>
<group1_attr2>0</group1_attr2>
</Test>
if saving as group2
<?xml version="1.0" encoding="utf-8"?>
<Test xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org /2001/XMLSchema">
<group2_attr1>0</group2_attr1>
</Test>
Is there a way how to do it in a "clean" way ? If not in xmlserialization then in binary perhaps ?
Then I would like to know what is the best way how to merge those two files into single instance of Test. (Note that fields from group1 and group2 do not overlap)
Here's code demonstrating how to serialize like you want, however, deserialization will be more tricky because it's not straightforward to deserialize into an existing instance (see How to use XmlSerializer to deserialize into an existing instance?).
public class Test
{
public int group1_attr1;
public int group1_attr2;
public int group2_attr1;
}
class Program
{
static void Main(string[] args)
{
System.Xml.Serialization.XmlAttributes xa =
new System.Xml.Serialization.XmlAttributes();
xa.XmlIgnore = true;
System.Xml.Serialization.XmlAttributeOverrides xo1 =
new System.Xml.Serialization.XmlAttributeOverrides();
xo1.Add(typeof(Test), "group1_attr1", xa);
xo1.Add(typeof(Test), "group1_attr2", xa);
System.Xml.Serialization.XmlAttributeOverrides xo2 =
new System.Xml.Serialization.XmlAttributeOverrides();
xo2.Add(typeof(Test), "group2_attr1", xa);
System.Xml.Serialization.XmlSerializer xs1 =
new System.Xml.Serialization.XmlSerializer(typeof(Test), xo1);
System.Xml.Serialization.XmlSerializer xs2 =
new System.Xml.Serialization.XmlSerializer(typeof(Test), xo2);
Test t1 = new Test();
t1.group1_attr1 = 1;
t1.group1_attr2 = 2;
t1.group2_attr1 = 3;
using (System.IO.StringWriter sw = new System.IO.StringWriter())
{
xs1.Serialize(sw, t1);
Console.WriteLine(sw);
}
using (System.IO.StringWriter sw = new System.IO.StringWriter())
{
xs2.Serialize(sw, t1);
Console.WriteLine(sw);
}
}
}
The deserialization could maybe merge the XML before deserializing:
Test t2 = new Test();
System.Xml.Serialization.XmlSerializer xs3 = new System.Xml.Serialization.XmlSerializer(typeof(Test));
string mergedXml = MergeSerializedXml(group1, group2);
using (System.IO.StringReader sr = new System.IO.StringReader(mergedXml))
{
t2 = (Test)xs3.Deserialize(sr);
}
...
static string MergeSerializedXml(string x1, string x2)
{
System.Xml.XmlDocument xd = new System.Xml.XmlDocument();
xd.LoadXml(x1);
System.Xml.XmlReaderSettings xrs = new System.Xml.XmlReaderSettings();
xrs.IgnoreWhitespace = true;
using (System.Xml.XmlReader xr = System.Xml.XmlReader.Create(new System.IO.StringReader(x2), xrs))
{
while (xr.Read() && !xr.IsStartElement())
;
xr.MoveToContent();
xr.Read();
System.Xml.XmlNode xn = xd.ChildNodes[1];
do
{
xn.AppendChild(xd.ReadNode(xr));
} while (xr.NodeType != System.Xml.XmlNodeType.EndElement);
}
return xd.OuterXml;
}
Composition will sort of allow you to do this, http://en.wikipedia.org/wiki/Composition_over_inheritance. If you serialize CompositeGroup1 then stick it in an instance of CompositeData. Do the same for CompositeGroup2. There's no way to do what you are asking for, as you asked it, with the built in serializers. When you serialize data it will always generate the full serialization for that type.
class CompositeData
{
public CompositeGroup1 Group1 { get; set; }
public CompositeGroup2 Group2 { get; set; }
}
class CompositeGroup1
{
public int Attr1 { get; set; }
public int Attr2 { get; set; }
}
class CompositeGroup2
{
public int Attr1 { get; set; }
}
You could also consider making CompositeData implement abstract classes, one for each grouping. You can use some other JSON serializers that use the type passed in instead of the runtime type to generate the serialized data.
var json1 = serializer.Serialize<AbstractGroup1>(new CompositeData());
var json2 = serializer.Serialize<AbstractGroup2>(new CompositeData());
However, putting them back together again would still be your problem.
You can loook into my project, Xml Serialization Framework, that will use interfaces idiom to solve the same issue as you might have
Example:
To serialize class instance/object
class Person
{
public string Name { get; set; }
}
you would probably declare interfaces to use in metadata driven xml runtime serialization:
interface IPersonRoot
{
string Name { get; set; }
}
[XmlRootSerializer("Body")]
interface IPersonCustomRoot
{
string Name { get; set; }
}
interface IPersonCustomAttribute
{
[XmlAttributeRuntimeSerializer("Id")]
string Name { get; set; }
}
This framework supports even working with partial update of living instances using particular interfaces/XML formats:
to/from XML using IPersonRoot interface:
<Person>
<Name>John Doe</Name>
</Person>
to/from XML using IPersonCustomRoot interface:
<Body>
<Name>John Doe</Name>
</Body>
to/from XML using IPersonCustomAttribute interface:
<Person Id="John Doe" />
Have a nice day,
Cheers ;)
Artur M.

How to add XmlInclude attribute dynamically

I have the following classes
[XmlRoot]
public class AList
{
public List<B> ListOfBs {get; set;}
}
public class B
{
public string BaseProperty {get; set;}
}
public class C : B
{
public string SomeProperty {get; set;}
}
public class Main
{
public static void Main(string[] args)
{
var aList = new AList();
aList.ListOfBs = new List<B>();
var c = new C { BaseProperty = "Base", SomeProperty = "Some" };
aList.ListOfBs.Add(c);
var type = typeof (AList);
var serializer = new XmlSerializer(type);
TextWriter w = new StringWriter();
serializer.Serialize(w, aList);
}
}
Now when I try to run the code I got an InvalidOperationException at last line saying that
The type XmlTest.C was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.
I know that adding a [XmlInclude(typeof(C))] attribute with [XmlRoot] would solve the problem. But I want to achieve it dynamically. Because in my project class C is not known prior to loading. Class C is being loaded as a plugin, so it is not possible for me to add XmlInclude attribute there.
I tried also with
TypeDescriptor.AddAttributes(typeof(AList), new[] { new XmlIncludeAttribute(c.GetType()) });
before
var type = typeof (AList);
but no use. It is still giving the same exception.
Does any one have any idea on how to achieve it?
Two options; the simplest (but giving odd xml) is:
XmlSerializer ser = new XmlSerializer(typeof(AList),
new Type[] {typeof(B), typeof(C)});
With example output:
<AList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ListOfBs>
<B />
<B xsi:type="C" />
</ListOfBs>
</AList>
The more elegant is:
XmlAttributeOverrides aor = new XmlAttributeOverrides();
XmlAttributes listAttribs = new XmlAttributes();
listAttribs.XmlElements.Add(new XmlElementAttribute("b", typeof(B)));
listAttribs.XmlElements.Add(new XmlElementAttribute("c", typeof(C)));
aor.Add(typeof(AList), "ListOfBs", listAttribs);
XmlSerializer ser = new XmlSerializer(typeof(AList), aor);
With example output:
<AList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<b />
<c />
</AList>
In either case you must cache and re-use the ser instance; otherwise you will haemorrhage memory from dynamic compilation.
Building on Marc's first answer (I only have to read, so I don't need to prevent the weird output), I use a more dynamic/generic type-array to account for unknown types, inspired by this codeproject.
public static XmlSerializer GetSerializer()
{
var lListOfBs = (from lAssembly in AppDomain.CurrentDomain.GetAssemblies()
from lType in lAssembly.GetTypes()
where typeof(B).IsAssignableFrom(lType)
select lType).ToArray();
return new XmlSerializer(typeof(AList), lListOfBs);
}
(One could probably make it more efficient, e.g. using a static or read-only type-array in stead of a local variable. That would avoid repeatedly using Reflection. But I don't know enough about when assemblies get loaded and classes and properties get initialized, to know if that would get you into trouble. My usage is not that much, to take the time to investigate this all, so I just use the same Reflection multiple times.)
Have a look at the documentation of XmlSerializer. There is a constructor which expects known types as the second parameter. That should work fine for you use case.
I'm don't think attributes can be applied at runtime, as they are used to create Meta-data at the CIL code.

Categories