Xml Serialize Interface Typed Members

30 באפריל 2012

תגיות: ,
3 תגובות

If you try to serialize a class that has a public member of an interface type to xml file using the regular .net XmlSerializer, you will get an error: “Cannot serialize member {m} of type {t} because it is an interface.” ({m} and {t} are placeholders).
In this post I suggest a workaround to this issue.

Consider the following scenario:

You have an object model like this:

   1: public class Car

   2: {

   3:     public string Model { get; set; }

   4:     public int Year { get; set; }

   5:     public IEngine Engine { get; set; }

   6: }

   7:  

   8: public interface IEngine

   9: {

  10:     void Work();

  11: }

  12:  

  13: public class ElectricEngine : IEngine

  14: {

  15:     int batteryPrecentageLeft;

  16:     public int BatteryPrecentageLeft

  17:     {

  18:         get { return this.batteryPrecentageLeft; }

  19:         set { this.batteryPrecentageLeft = value; }

  20:     }

  21:  

  22:     void IEngine.Work() { }

  23: }

  24:  

  25: public class InternalCombustionEngine : IEngine

  26: {

  27:     int gasLitersLeft;

  28:     public int GasLitersLeft

  29:     {

  30:         get { return this.gasLitersLeft; }

  31:         set { this.gasLitersLeft = value; }

  32:     }

  33:  

  34:     void IEngine.Work() { }

  35: }

And you use it like so:

   1: Car myCar = new Car();

   2: myCar.Model = "Ford Focus";

   3: myCar.Year = 2011;

   4: myCar.Engine = new ElectricEngine() { BatteryPrecentageLeft = 70 };

   5:  

   6: myCar.Engine.Work();

If you would try to serialize myCar using the regular XmlSerializer, you will get the error above.

There are many reasons why you shouldn’t want to serialize an interface typed member. I will not dive into this debate right now. I assume that you have considered this before, and are just looking for a workaround.

So first lets understand what we are trying to achieve.

In the example above, I expect the serialized output of myCar to be something like this:

   1: <Car xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

   2:   <Model>Ford Focus</Model>

   3:   <Year>2011</Year>

   4:   <Engine>

   5:     <ElectricEngine FullAssemblyQualifiedTypeName="Common.ElectricEngine, Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">

   6:       <BatteryPrecentageLeft>70</BatteryPrecentageLeft>

   7:     </ElectricEngine>

   8:   </Engine>

   9: </Car>

I have asked a question on StackOverflow, and Jens Granlund has pointed me in the right direction.

The workaround that I have implemented consists of the following steps:

  1. Mark all interface typed members with the XmlIgnoreAttribute, that tells the XmlSerializer to not serialize those members.
  2. Now serialize the class to produce an output similar to the one above, but without the problematic members.
  3. For-each member marked with the attribute, get it’s current value, and that value’s type. That can only be done in runtime.
  4. Use that type to create a standard XmlSerializer, and use that serializer to serialize that value.
  5. Inject those serialized values to the parent class’s serialized xml.
  6. In order to allow regular use of the XmlIgnoreAttribute, implement a new attribute that act like it.
  7. During deserialization perform the same process in reverse.

I have followed that basic recipe to create my own “RuntimeXmlSerializer”. You use it the same way you would use the standard XmlSerializer.

   1: public class RuntimeXmlSerializerAttribute : XmlIgnoreAttribute { }

   2:  

   3: public class RuntimeXmlSerializer

   4: {

   5:     private Type m_type;

   6:     private XmlSerializer m_regularXmlSerializer;

   7:  

   8:     private const string k_FullClassNameAttributeName = "FullAssemblyQualifiedTypeName";

   9:  

  10:     public RuntimeXmlSerializer(Type i_subjectType)

  11:     {

  12:         this.m_type = i_subjectType;

  13:         this.m_regularXmlSerializer = new XmlSerializer(this.m_type);

  14:     }

  15:  

  16:     public void Serialize(object i_objectToSerialize, Stream i_streamToSerializeTo)

  17:     {

  18:         StringWriter sw = new StringWriter();

  19:         this.m_regularXmlSerializer.Serialize(sw, i_objectToSerialize);

  20:         XDocument objectXml = XDocument.Parse(sw.ToString());

  21:         sw.Dispose();

  22:         SerializeExtra(i_objectToSerialize,objectXml);

  23:         string res = objectXml.ToString();

  24:         byte[] bytesToWrite = Encoding.UTF8.GetBytes(res);

  25:         i_streamToSerializeTo.Write(bytesToWrite, 0, bytesToWrite.Length);

  26:     }

  27:  

  28:     public object Deserialize(Stream i_streamToSerializeFrom)

  29:     {

  30:         string xmlContents = new StreamReader(i_streamToSerializeFrom).ReadToEnd();

  31:         StringReader sr;

  32:         sr = new StringReader(xmlContents);

  33:         object res = this.m_regularXmlSerializer.Deserialize(sr);

  34:         sr.Dispose();

  35:         sr = new StringReader(xmlContents);

  36:         XDocument doc = XDocument.Load(sr);

  37:         sr.Dispose();

  38:         deserializeExtra(res, doc);

  39:         return res;

  40:     }

  41:  

  42:     private void deserializeExtra(object i_desirializedObject, XDocument i_xmlToDeserializeFrom)

  43:     {

  44:         IEnumerable propertiesToDeserialize = i_desirializedObject.GetType()

  45:             .GetProperties().Where(p => p.GetCustomAttributes(true)

  46:                 .FirstOrDefault(a => a.GetType() ==

  47:                     typeof(RuntimeXmlSerializerAttribute)) != null);

  48:         foreach (PropertyInfo prop in propertiesToDeserialize)

  49:         {

  50:             XElement propertyXml = i_xmlToDeserializeFrom.Descendants().FirstOrDefault(e =>

  51:                 e.Name == prop.Name);

  52:             if (propertyXml == null) continue;

  53:             XElement propertyValueXml = propertyXml.Descendants().FirstOrDefault();

  54:             Type type = Type.GetType(propertyValueXml.Attribute(k_FullClassNameAttributeName).Value.ToString());

  55:             XmlSerializer srl = new XmlSerializer(type);

  56:             object deserializedObject = srl.Deserialize(propertyValueXml.CreateReader());

  57:             prop.SetValue(i_desirializedObject, deserializedObject, null);

  58:         }

  59:     }

  60:  

  61:     private void SerializeExtra(object objectToSerialize, XDocument xmlToSerializeTo)

  62:     {

  63:         IEnumerable propertiesToSerialize =

  64:             objectToSerialize.GetType().GetProperties().Where(p =>

  65:                 p.GetCustomAttributes(true).FirstOrDefault(a =>

  66:                     a.GetType() == typeof(RuntimeXmlSerializerAttribute)) != null);

  67:         foreach (PropertyInfo prop in propertiesToSerialize)

  68:         {

  69:             XElement serializedProperty = new XElement(prop.Name);

  70:             serializedProperty.AddFirst(serializeObjectAtRuntime(prop.GetValue(objectToSerialize, null)));

  71:             xmlToSerializeTo.Descendants().First().Add(serializedProperty); //TODO

  72:         }

  73:     }

  74:  

  75:     private XElement serializeObjectAtRuntime(object i_objectToSerialize)

  76:     {

  77:         Type t = i_objectToSerialize.GetType();

  78:         XmlSerializer srl = new XmlSerializer(t);

  79:         StringWriter sw = new StringWriter();

  80:         srl.Serialize(sw, i_objectToSerialize);

  81:         XElement res = XElement.Parse(sw.ToString());

  82:         sw.Dispose();

  83:         XAttribute fullClassNameAttribute = new XAttribute(k_FullClassNameAttributeName, t.AssemblyQualifiedName);

  84:         res.Add(fullClassNameAttribute);

  85:  

  86:         return res;

  87:     }

  88: }

Notice that this solution doesn’t handle situations in which the interface typed member is not a direct descendent of the type being serialized. In example if Car has a member of type List<IWheel> or if Car has a member of type Dashboard which has a member f type IGauge, than serialization will fail.

This can be solved by recursively performing the “Runtime” serialization on all members, until we reach a primitive member which we can serialize using the regular serializer.

You can find the Visual Studio project in this link:

http://blogs.microsoft.co.il/files/folders/1078707/download.aspx

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

3 תגובות

  1. tietrinny2 במאי 2013 ב 14:14

    Can I just say what a relief to locate an individual who basically knows what theyre talking about on the web. You absolutely know the best way to bring an problem to light and make it fundamental. More folks should read this and have an understanding of this side of the story. I cant believe youre not far more favorite due to the fact you surely have the gift.

    [URL=http://www.lululemonoutletonsale.com/lululemon-tops/lululemon-in-stride-jackets.html]lululemon athletica careers[/URL]

    הגב
  2. http://www.btk-led.com/images/Jaguarsjerseys.aspx?54 באוקטובר 2013 ב 4:38

    The brief video also shows Alexis, armed with a Remington shotgun and wearing dark clothing, descending a stairway and walking along corridors in a crouch position, weapon held at the ready. People can be glimpsed at the end of one corridor. Alexis peeks around corners and, at one point, aims the shotgun into a room but does not fire. Parlave said Alexis, a government technology contractor, had in his possession the shotgun, which had a sawed-off barrel and stock, and a pistol he obtained during the shooting.
    larry fitzgerald football

    הגב