views:

2746

answers:

3

I'm attempting to deserialize a custom class via the XmlSerializer and having a few problems, in the fact that I don't know the type that I'm going to be deserializing (it's pluggable) and I'm having difficulty determining it.

I found this post which looks similar but can't quite get it to work with my approach because I need to deserialize an interface which is XmlSerializable.

What I've currently got is of the form. Note that I expect and need to be able to handle both class A and class B to be implemented via a plugin. So if I can avoid using the IXmlSerializable (which I don't think I can) then that would be great.

The ReadXml for A is what I'm stuck on. However if there are other changes that I can make to improve the system then I'm happy to do so.

public class A : IXmlSerializable
{
   public IB MyB { get; set;}

   public void ReadXml(System.Xml.XmlReader reader)
   {
      // deserialize other member attributes

      SeekElement(reader, "MyB");
      string typeName = reader.GetAttribute("Type");

      // Somehow need to the type based on the typename. From potentially 
      //an external assembly. Is it possible to use the extra types passed 
      //into an XMlSerializer Constructor???
      Type bType = ???

      // Somehow then need to deserialize B's Members
      // Deserialize X
      // Deserialize Y
   }

   public void WriteXml(System.Xml.XmlWriter writer)
   {
      // serialize other members as attributes

      writer.WriteStartElement("MyB");
      writer.WriteAttributeString("Type", this.MyB.GetType().ToString());
      this.MyB.WriteXml(writer);
      writer.WriteEndElement();
   }

   private void SeekElement(XmlReader reader, string elementName)
   {
      ReaderToNextNode(reader);
      while (reader.Name != elementName)
      {
         ReaderToNextNode(reader);
      }
   }

   private void ReaderToNextNode(XmlReader reader)
   {
      reader.Read();
      while (reader.NodeType == XmlNodeType.Whitespace)
      {
         reader.Read();
      }
   }
}

public interface IB : IXmlSerializable
{
}

public class B : IB
{

     public void ReadXml(XmlReader reader)
     {
         this.X = Convert.ToDouble(reader.GetAttribute("x"));
         this.Y = Convert.ToDouble(reader.GetAttribute("y"));
     }

   public void WriteXml(XmlWriter writer)
   {
      writer.WriteAttributeString("x", this.X.ToString());
      writer.WriteAttributeString("y", this.Y.ToString());
   }
}

NOTE : Updated as I realised B was supposed to use interface IB. Sorry for slightly wrong question.

+2  A: 

To create an instance from a string, use one of the overloads of Activator.CreateInstance. To just get a type with that name, use Type.GetType.

John Saunders
That's an interesting approach. So your suggesting at that point call the ReadXml() on that created type to initialize the members? That might work depending on whether I can get the type + create one of a possibly external type to my library.
Ian
I was responding to your comment where you said "Somehow need to the type based on the typename."
John Saunders
Thanks John. By reading out the type from the XML, creating an instance I was then able to de-serialize it using the ReadXml method of the created instance. Works a treat and works fine with my Interface.
Ian
A: 

I'd use xpath to quickly figure out whether the input xml contains class A or class B. Then deserialize it based on that.

Arnshea
booo for xpath :(
Stan R.
heh, hey i'm a sql junkie too though I've stayed away from the more exotic features of xpath... :)
Arnshea
Could you provide a very brief example? I've not really used xpath. Just wondering if it is worth looking into. Wanted to deal with the XML directly as little as possible though.
Ian
A: 

I don't think you need to implement IXmlSerializable...

Since you don't know the actual types before runtime, you can dynamically add attribute overrides to the XmlSerializer. You just need to know the list of types that inherit from A. For instance, if you use A as a property of another class :

public class SomeClass
{
    public A SomeProperty { get; set; }
}

You can dynamically apply XmlElementAttributes for each derived type to that property :

XmlAttributes attr = new XmlAttributes();
var candidateTypes = from t in AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes())
                     where typeof(A).IsAssignableFrom(t) && !t.IsAbstract
                     select t;
foreach(Type t in candidateTypes)
{
    attr.XmlElements.Add(new XmlElementAttribute(t.Name, t));
}

XmlAttributeOverrides overrides = new XmlAttributeOverrides();
overrides.Add(typeof(SomeClass), "SomeProperty", attr);

XmlSerializer xs = new XmlSerializer(typeof(SomeClass), overrides);
...

This is just a very basic example, but it shows how to apply XML serialization attributes at runtime when you can't do it statically.

Thomas Levesque
Thomas, this looks similar to Marks answer in the other post that I referenced, and is more of an idea solution for me I think.The problem however lies in the A.MyB property (I updated the question to make it clearer). When I've tried to serialize this before, I get an error because MyB is actually an interface.Is there a way to combine the serialization/deserialization of this interface in your suggested approach?
Ian
No, unfortunately XML serialization doesn't serialize interfaces... in that case I'm afraid you will have to use IXmlSerializable.
Thomas Levesque
Yeah, I thought as much... I'm starting to wonder if maybe my interface should instead be an abstract class. I'm assuming then it would work and hierarchies of classes (e.g. B implemeneting AbstractB) would be deserialized, rather than attempting to deserialize an AbstractB directly.
Ian
Yes, it would be simpler with an abstract class...
Thomas Levesque