Introduction
Have you ever wanted to serialize a configuration object that has a collection that contains an interface or abstract class? Do you not know all the types that you are going to possibly serialize? Well, if that's the case, then I may be able to help you out.
Using the code
OK.. Let's get started. You'll first want to take the following helper class and paste it into your project. It will help you with serializing objects into XML in one line of code.
public class GenericSerializer
{
public static string Serialize<T>(T obj)
{
XmlSerializer xs = null;
StringWriter sw = null;
try
{
xs = new XmlSerializer(typeof(T));
sw = new StringWriter();
xs.Serialize(sw, obj);
sw.Flush();
return sw.ToString();
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (sw != null)
{
sw.Close();
sw.Dispose();
}
}
}
public static string Serialize<T>(T obj, Type[] extraTypes)
{
XmlSerializer xs = null;
StringWriter sw = null;
try
{
xs = new XmlSerializer(typeof(T), extraTypes);
sw = new StringWriter();
xs.Serialize(sw, obj);
sw.Flush();
return sw.ToString();
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (sw != null)
{
sw.Close();
sw.Dispose();
}
}
}
public static void Serialize<T>(T obj, XmlWriter writer)
{
XmlSerializer xs = new XmlSerializer(typeof(T));
xs.Serialize(writer, obj);
}
public static void Serialize<T>(T obj, XmlWriter writer, Type[] extraTypes)
{
XmlSerializer xs = new XmlSerializer(typeof(T), extraTypes);
xs.Serialize(writer, obj);
}
public static T Deserialize<T>(XmlReader reader)
{
XmlSerializer xs = new XmlSerializer(typeof(T));
return (T)xs.Deserialize(reader);
}
public static T Deserialize<T>(XmlReader reader, Type[] extraTypes)
{
XmlSerializer xs = new XmlSerializer(typeof(T), extraTypes);
return (T)xs.Deserialize(reader);
}
public static T Deserialize<T>(string XML)
{
if (XML == null || XML == string.Empty)
return default(T);
XmlSerializer xs = null;
StringReader sr = null;
try
{
xs = new XmlSerializer(typeof(T));
sr = new StringReader(XML);
return (T)xs.Deserialize(sr);
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (sr != null)
{
sr.Close();
sr.Dispose();
}
}
}
public static T Deserialize<T>(string XML, Type[] extraTypes)
{
if (XML == null || XML == string.Empty)
return default(T);
XmlSerializer xs = null;
StringReader sr = null;
try
{
xs = new XmlSerializer(typeof(T), extraTypes);
sr = new StringReader(XML);
return (T)xs.Deserialize(sr);
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (sr != null)
{
sr.Close();
sr.Dispose();
}
}
}
public static void SaveAs<T>(T Obj, string FileName,
Encoding encoding, Type[] extraTypes)
{
if (File.Exists(FileName))
File.Delete(FileName);
DirectoryInfo di = new DirectoryInfo(Path.GetDirectoryName(FileName));
if (!di.Exists)
di.Create();
XmlDocument document = new XmlDocument();
XmlWriterSettings wSettings = new XmlWriterSettings();
wSettings.Indent = true;
wSettings.Encoding = encoding;
wSettings.CloseOutput = true;
wSettings.CheckCharacters = false;
using (XmlWriter writer = XmlWriter.Create(FileName, wSettings))
{
if (extraTypes != null)
Serialize<T>(Obj, writer, extraTypes);
else
Serialize<T>(Obj, writer);
writer.Flush();
document.Save(writer);
}
}
public static void SaveAs<T>(T Obj, string FileName, Type[] extraTypes)
{
SaveAs<T>(Obj, FileName, Encoding.UTF8, extraTypes);
}
public static void SaveAs<T>(T Obj, string FileName, Encoding encoding)
{
SaveAs<T>(Obj, FileName, encoding, null);
}
public static void SaveAs<T>(T Obj, string FileName)
{
SaveAs<T>(Obj, FileName, Encoding.UTF8);
}
public static T Open<T>(string FileName, Type[] extraTypes)
{
T obj = default(T);
if (File.Exists(FileName))
{
XmlReaderSettings rSettings = new XmlReaderSettings();
rSettings.CloseInput = true;
rSettings.CheckCharacters = false;
using (XmlReader reader = XmlReader.Create(FileName, rSettings))
{
reader.ReadOuterXml();
if (extraTypes != null)
obj = Deserialize<T>(reader, extraTypes);
else
obj = Deserialize<T>(reader);
}
}
return obj;
}
public static T Open<T>(string FileName)
{
return Open<T>(FileName, null);
}
}
OK.. Now that we have that out of the way, let's take a look at our real problem. Take for instance, we have an abstract class, Animal
. Then, we have two classes that inherit this class, Human
and Dog
.
public abstract class Animal
{
public abstract int Legs
{
get;
set;
}
public abstract bool HasTail
{
get;
set;
}
}
public class Human : Animal
{
public Human()
{
this.Legs = 2;
this.HasTail = false;
}
public override int Legs
{
get;
set;
}
public override bool HasTail
{
get;
set;
}
}
public class Dog : Animal
{
public Dog()
{
this.Legs = 4;
this.HasTail = true;
this.Breed = "Black Lab";
}
public override int Legs
{
get;
set;
}
public override bool HasTail
{
get;
set;
}
public string Breed
{
get;
set;
}
}
OK... Now, we have an object that we want to be able to serialize. It's called HouseHold
, and it has a collection of Animal
s as one of its properties.
public class HouseHold
{
public HouseHold()
{
this.Residents = new List<Animal>();
}
public List<Animal> Residents
{
get;
set;
}
}
Now, we want to serialize this object.
try
{
HouseHold hh = new HouseHold();
hh.Residents.Add(new Human());
hh.Residents.Add(new Human());
hh.Residents.Add(new Dog());
string xml = GenericSerializer.Serialize<HouseHold>(hh);
Console.WriteLine(xml);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
Console.Read();
You'll notice that we get the following exception:
"There was an error generating the XML document."
with an inner exception of:
"The type ClassLibrary1.Human was not expected.
Use the XmlInclude or SoapInclude attribute to specify types that are not known statically."
The keyword there is "statically". Yes, we could go and statically place the XmlInclude
attribute on top of the class, but what if we don't know all the classes that inherit from Animal
? What if our application is pluggable and needs to be able to use types that are generated by customers? Well, let's move forward, and I'll show you how this can be done.
The first thing we'll need to do is have our HouseHold
class implement IXmlSerializable
so that we can intercept the serialization of this object and serialize it ourselves. Next, we'll need to create a method that retrieves all of our types. In this example, we'll query the current assembly using Reflection for any class that inherits Animal
.
public class HouseHold : IXmlSerializable
{
private static Type[] _animalTypes;
static HouseHold()
{
_animalTypes = GetAnimalTypes().ToArray();
}
public HouseHold()
{
this.Residents = new List<Animal>();
}
public List<Animal> Residents
{
get;
set;
}
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
bool wasEmpty = reader.IsEmptyElement;
reader.Read();
if (wasEmpty)
return;
reader.MoveToContent();
reader.ReadStartElement("Residents");
this.Residents = GenericSerializer.Deserialize<List<Animal>>(reader, _animalTypes);
reader.ReadEndElement();
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement("Residents");
GenericSerializer.Serialize<List<Animal>>(this.Residents, writer, _animalTypes);
writer.WriteEndElement();
}
#endregion
public static List<Type> GetAnimalTypes()
{
List<Type> types = new List<Type>();
Assembly asm = typeof(HouseHold).Assembly;
Type tAnimal = typeof(Animal);
foreach (Type currType in asm.GetTypes())
{
if (!currType.IsAbstract
&& !currType.IsInterface
&& tAnimal.IsAssignableFrom(currType))
types.Add(currType);
}
return types;
}
}
OK... Now, we will run our test code, and everything should run through just fine. I hope this helped you out a lot. Let me know what you think.