Getting to the Root in Serialization using C#

Posted in software by Christopher R. Wirz on Thu Jul 30 2015



C# code generated from a schema is going to have many custom attributes. Sometimes, these attributes modify the behavior of serialization. A common attribute designed to do this is the Root attribute.

Note: .Net 4.5 is required for the following examples.

Below is an example of xsd generated C# code.


[System.Xml.Serialization.XmlRootAttribute("ClassObject", Namespace = "http://my.namespace.com/")]
public partial class MyClassObject
{
	public string MyProperty { get; set;}
}

In this example, a serialization of MyClassObject would create a root element of ClassObject. This might lead to some confusion if we are blindly deserializing xml content.

First, assume we have all the known class types in the assembly. Then, we iterate through checking the CustomAttribute ArgumentType Values.


public static partial class TypeExtensions
	
	/// <summary>
	///     Finds the type by its name.
	/// </summary>
	/// <param name="name">The name of the class.</param>
	/// <returns>A type or null</returns>
	public static Type FindTypeByName(string name)
	{
		var types = Assembly.GetCallingAssembly().GetTypes().ToList();
		
		// First check for a matching name
		var t = types.FirstOrDefault(c => c.Name == name);
		if (t != null) return t;
		
		// Then iterate through the AttributeType Values
		foreach (var type in types)
		{
			if (type.CustomAttributes == null) continue;
			foreach (
				var customAttribute in type.CustomAttributes.Where(a => a.AttributeType.Name == "XmlRootAttribute"))
			{
				foreach (var constructorArgument in customAttribute.ConstructorArguments)
				{
					if (constructorArgument.ArgumentType == null) continue;
					if (constructorArgument.ArgumentType.Name.ToLower().Trim() != "string") continue;
					if (constructorArgument.Value.ToString().Trim() != name.Trim()) continue;
					return type;
				}
			}
		}
		return null;
	}

	/// <summary>
	///     Gets an object from the XML.
	/// </summary>
	/// <param name="xml">The XML.</param>
	/// <returns>An object type or null</returns>
	public static object FromXml(string xml)
	{
		try
		{
			if (string.IsNullOrWhiteSpace(xml)) return null;
			var document = new XmlDocument();
			document.LoadXml(xml);
			if (document.DocumentElement == null) return null;

			// Find the type
			var type = FindTypeByName(document.DocumentElement.Name) ??
			   FindTypeByName(document.DocumentElement.Name.Split(':').Last());
			if (type == null) return null;

			// Deserialize the contents
			var xmlSerializer = new XmlSerializer(type);
			using (var streamReader = new StringReader(xml))
			using (var xmlReader = XmlReader.Create(streamReader))
			{
				return xmlSerializer.Deserialize(xmlReader);
			}
		}
		catch (Exception ex)
		{
			Console.WriteLine(ex.Message);
		}
		return null;
	}
}

Note: You can use LINQ to simplify these statements

For example, the XML in the file "ClassObject.xml"


<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<ClassObject xmlns="http://my.namespace.com/">
  <MyProperty>Test Property</MyProperty>
</ClassObject>

Can be deserialized to a MyClassObject as follows:


MyClassObject mco = (MyClassObject)TypeExtensions.FromXml(File.ReadAllText("ClassObject.xml"));