Serializing C# objects using the .NET framework is a simple task, as is shaping the XML which results from serializing a C# object in terms of XML elements, attributes and namespaces.
However, whats not so straightforward, or at least sparsely documented, is controlling what if any namespace and Xml declarations are emitted during serialization.
<?xml version="1.0" encoding="utf-16"?> <MyDocumentClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="https://platinumdogs.wordpress.com/client" serializer="Custom_XML_Serialization.StandardXmlSerialization"> <Name>MyGreatDocument.docx</Name> <Folder>c:\my documents\</Folder> </MyDocumentClass>
The XML shown above was emitted by the standard XML serialization code provided by the .NET framework. In some cases you may have a class which needs to be serialized a number of different ways, perhaps omitting the namespace declarations or XML declaration as shown below.
<MyDocumentClass serializer="Custom_XML_Serialization.CustomXmlSerialization"> <Name>MyGreatDocument.docx</Name> <Folder>c:\my documents\</Folder> </MyDocumentClass>
This can be done using a combination of the XmlSerializer, XmlSerializerNamespaces and XmlWriterSettings classes. The code sample below demonstrates how to achieve this naked form of XML serialization.
public class CustomXmlSerialization { public virtual XmlSerializer GetSerializer(Type objectType) { // get the XmlRoot attribute off the object type and use the // XmlRoot name or the object type name as the XML root element name var ca = (XmlRootAttribute[]) objectType.GetCustomAttributes(typeof (XmlRootAttribute), false); var xao = new XmlAttributeOverrides(); var root = new XmlAttributes { XmlRoot = new XmlRootAttribute(ca != null && ca.Length > 0 && !string.IsNullOrEmpty(ca[0].ElementName) ? ca[0].ElementName : objectType.Name), Xmlns = false }; xao.Add(objectType, root); var s = new XmlSerializer(objectType, xao); return s; } public virtual XmlSerializerNamespaces GetSerializerNamespaces() { var ns = new XmlSerializerNamespaces(); ns.Add("", ""); return ns; } public virtual XmlWriterSettings GetSerializerWriterSettings() { var xs = new XmlWriterSettings {OmitXmlDeclaration = true}; return xs; } public string SerializeToXml(object objectToSerialize) { var t = objectToSerialize.GetType(); var s = GetSerializer(t); var xs = GetSerializerWriterSettings(); var sb = new StringBuilder(); using (var x = XmlWriter.Create(new StringWriter(sb), xs)) { var ns = GetSerializerNamespaces(); if (ns == null) s.Serialize(x, objectToSerialize); else s.Serialize(x, objectToSerialize, ns); x.Flush(); } // add the serializer typename as an attribute var xd = XElement.Load(new StringReader(sb.ToString())); xd.Add(new XAttribute("serializer", GetType().FullName)); sb = new StringBuilder(); using (var x = XmlWriter.Create(new StringWriter(sb), xs)) { xd.Save(x); x.Flush(); } return sb.ToString(); } }
However this will cause a problem when you attempt to deserialize the XML back into an object instance – an exception will be thrown since the standard XML serialization code will be expecting, at least, the namespace declarations, which are now missing.
The solution is to do the deserialization using an XmlSerializer instance created in the same way as that used to do the serialization. But, that would require that a deserializer knows how the XML was originally serialized, and this kind of A-priori knowledge is probably not a good thing.
The solution here, is to have the serializer record it’s type name as part of the XML serialization, in this case as an XML attribute on the root document element, which is what the last part of the SerializeToXml(…) method does. The deserializer can now determine the serializer originally used and using reflection can create an instance of that type which it can use to perform the deserialization, such a deserialization method is shown below;
public TObjectType DeserializeFromXml<TObjectType>(string xml) where TObjectType : class, new() { // get the serializer typename used (xml attribute) var xd = XElement.Load(new StringReader(xml)); var xaSerializer = xd.Attribute("serializer"); Type typeSerializer; string typeSerializerName; if (xaSerializer == null || string.IsNullOrEmpty(xaSerializer.Value)) { // default case, the serializer attribute does not exist or is empty typeSerializer = typeof (CustomXmlSerialization); typeSerializerName = typeSerializer.FullName; } else { // get a type reference to the serializer type typeSerializerName = xaSerializer.Value; typeSerializer = Assembly.GetExecutingAssembly().GetType(typeSerializerName); if (typeSerializer == null) throw new Exception( string.Format("The serializer type {0} cannot be loaded or was not found in the current assembly {1}", typeSerializerName, Assembly.GetExecutingAssembly().FullName)); } // create serializer type instance var stype = (CustomXmlSerialization) Activator.CreateInstance(typeSerializer); if (stype == null) throw new Exception(string.Format("An instance of serializer type {0} cannot be created by the current assembly {1}", typeSerializerName, Assembly.GetExecutingAssembly().FullName)); // deserialize var t = typeof (TObjectType); var s = stype.GetSerializer(t); using (var x = new XmlTextReader(new StringReader(xml))) { if (!s.CanDeserialize(x)) throw new Exception("Cannot deserialize supplied xml to an object of type " + t.Name); var o = (TObjectType) s.Deserialize(x); return o; } }
This kind serialization/deserialization process could easily be worked into a more sensible architecture as part of a serializable object/entity framework.
One thought on “.NET: Customizing XML Object Serialization”