.NET's XMLSerializer can be pretty stupid. It refuses to serialize an object if its properties are of a derived type. Consider the following example:
[Serializable]
public classPerson
{
public stringFirstName { get; set; }
public stringLastName { get; set; }
}
[Serializable]
public classSalesReceipt
{
publicPersonCustomer { get; set; }
}
[Serializable]
public classEmployee:Person
{
publicDateTime DateOfHire { get; set; }
}
public classtrythis
{
public voidmain()
{
Employee employee = newEmployee{ DateOfHire = DateTime.UtcNow };
SalesReceipt receipt = newSalesReceipt();
}
}
If you attempt to serialize the receipt object, the XML Serializer will throw the "Type Not Statically Known" exception. Basically its because the type of value in receipt.Customer isn't a Person instance, but rather a derived type (Employee). Microsoft's solution for this may actually be worse than the XMLSerializer limitation itself. Microsoft suggests you add an XMLInclude() attribute for every subtype you want to be able to support. In our example this means the Person class has to be marked up as follows:
[Serializable]
[XmlInclude(typeof(Employee))]
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
This means every time you extend your Person class with another derivation, you have to return to the Person class and add another XMLIncludeAttribute for it. That's pretty poor. But what if you don't have access to the base type? I'm not certain but I think you might be out of luck. That's unforgivable.
Luckily, Microsoft does allow you to pass in a list of "known" types to the XMLSerializer's constructor. This is the saving grace for XMLSerialization. By reflecting the object to serialize, we can build a full list of its type, all its base types, it property and field types and all their base types. At the end of the reflection, we ought to have every type we need to serialize the object. The following code reflects through both the type definition and the instance definition to ensure we get all defined and instance type in our known type list.
/// <summary>
/// reflects the object to serialize and builds a full list of related types for serialization.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static StringWriter SerializeWithKnownTypes(object obj)
{
List<Type> knownTypeList = new List<Type>();
// get the list of related types for this object's type and instance
Type[] knownTypes = getKnownTypesForInstance(obj, knownTypeList);
// create an instance of the Xml Serializer and pass in our list of related types
XmlSerializer serializer = new XmlSerializer(obj.GetType(), knownTypes);
// Let the serializer do its thing
StringWriter writer = new StringWriter();
serializer.Serialize(writer, obj);
return writer;
}
private static Type[] getKnownTypesForInstance(object obj, List<Type> knownTypeList)
{
// get the list of known types based on the object's type definition
getKnownTypes(obj.GetType(), knownTypeList);
// now we need to get the list of types from the instance, since the type definition will
// reference the base type, and we need the actual derived type that's used in the instance
PropertyInfo[] props = ReflectionTools.GetProperties(obj.GetType(), true);
foreach (PropertyInfo propertyInfo in props)
{
// get the value of the property from the instance
object propValue = ReflectionTools.GetPropertyValue(obj, propertyInfo.Name, false);
if (propValue == null)
getKnownTypes(propertyInfo.PropertyType, knownTypeList);
else
getKnownTypes(propValue.GetType(), knownTypeList);
}
return knownTypeList.ToArray();
}
private static Type[] getKnownTypes(Type type, List<Type> knownTypeList)
{
// Ignore .NET types and types already in the list
if (type.FullName.StartsWith("System.") || knownTypeList.Contains(type))
return knownTypeList.ToArray();
//add this type to the known types list
knownTypeList.Add(type);
// get the list of types for this object's fields
FieldInfo[] fields = type.GetFields();
foreach(FieldInfo field in fields)
{
getKnownTypes(field.FieldType, knownTypeList);
}
// get the list of types for this object's properties
PropertyInfo[] props = type.GetProperties();
foreach (PropertyInfo prop in props)
{
getKnownTypes(prop.PropertyType, knownTypeList);
}
// if this type isn't a base type, recursively call this method for the base type
// collecting the type information from the base type
if (type.BaseType != null)
getKnownTypes(type.BaseType, knownTypeList);
return knownTypeList.ToArray();
}
The above code references a helper class called ReflectionTools for some of the less interesting work. You can substitute you own reflection code there.
With the related types now "known" by the XmlSerializer, we can serialize the instance of our type with the XmlSerializer.