XmlSerializer and CollectionBase derived classes
This is something I've bumped my head with before and it happened again so this time I'll blog it...
Scenario: You've got a class derived from CollectionBase that you'd like to serialize using the XmlSerializer.
Example: (MyCollection that contains MyClasses, excuse my highly sophisticated code formatting but w.blogger doesn't play nicely with code)
[Serializable]
public class MyCollection : CollectionBase
{}
Output Required something like :
<?xml version="1.0" encoding="utf-8" ?>
<MyClasses>
<MyClass/>
<MyClass/>
</MyClasses>
First problem, there's a couple of requirements from the XmlSerializer for it to serialize correctly. This is not problems as most people already create their Collections this way and it makes sense if you look at the serialization process followed by XmlSerializer.
MSDN : The XmlSerializer gives special treatment to classes that implement IEnumerable or ICollection. A class that implements IEnumerable must implement a public Add method that takes a single parameter. The Add method's parameter must be of the same type as is returned from the Current property on the value returned from GetEnumerator, or one of that type's bases. A class that implements ICollection (such as CollectionBase) in addition to IEnumerable must have a public Item indexed property (indexer in C#) that takes an integer, and it must have a public Count property of type integer. The parameter to the Add method must be the same type as is returned from the Item property, or one of that type's bases. For classes implementing ICollection, values to be serialized will be retrieved from the indexed Item property, not by calling GetEnumerator.
The second problem is that you can't add any XmlAttributes to your Collectionbase derived class to control the structure of the root element, the XmlSerializer will break and you'll get a System.InvalidOperationException : System.InvalidOperationException : XML attributes may not be specified for the type YourCollection.
The way it serializes by default is not exactly what I'm after and what you end up with is something like the following :
Output without any Xml Attributes :
<?xml version="1.0" encoding="utf-8" ?>
<ArrayOfMyClasses xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<MyClass/>
.
.
</ArrayOfMyClasses>
Take two, I remember something about XmlAttributeOverrides that can be used to override the defaults for the serialization process. Add the overrides for my collection but this is subject to the same constraints and you get the same exception. This happens somewhere when trying to create the temporary assemblies used to do the serialization/deserialization. Anyone know where or what the restrictions is on this?
Then I found this method to regain control over the element name. Just by adding a new XmlRootAttribute to the XmlSerializer you get back control of the process. Thanks thanks! Ionic Shade,whoever you are!
Basically to control how your Collectionbase class is serialized I've added the following two helper methods based on his code. One that changes the root element name to something specified by the user and the second will just derive a root name from the Collection Class. So by using this the Collection Base is serializing/deserializing as I hoped.
public static XmlSerializer CreateXmlCollectionBaseSerializer(Type type, string root)
{
XmlRootAttribute ra = new XmlRootAttribute();
ra.Namespace = string.Empty;
ra.ElementName = root;
XmlSerializer ser = new XmlSerializer(type,ra);
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("","");
return ser;
}
public static XmlSerializer CreateXmlCollectionBaseSerializer(Type type)
{
return CreateXmlCollectionBaseSerializer(type,type.Name);
}
To also clear out the default namespaces have a look at his sample for more info.