118

I've got a very strange problem when working with .NET's XmlSerializer.

Take the following example classes:

public class Order 
{
    public PaymentCollection Payments { get; set; }

    //everything else is serializable (including other collections of non-abstract types)
}

public class PaymentCollection : Collection<Payment>
{
}

public abstract class Payment 
{
    //abstract methods
}

public class BankPayment : Payment
{
    //method implementations
}

AFAIK, there are three different methods to solve the InvalidOperationException that's caused by the serializer not knowing about the derived types of Payment.

1. Adding XmlInclude to the Payment class definition:

This is not possible due to all classes being included as external references over which I have no control of.

2. Passing the derived types' types during creation of the XmlSerializer instance

Doesn't work.

3. Defining XmlAttributeOverrides for the target property in order to override the property's default serialization (as explained in this SO post)

Also doesn't work (XmlAttributeOverrides initialization follows).

Type bankPayment = typeof(BankPayment);

XmlAttributes attributes = new XmlAttributes();
attributes.XmlElements.Add(new XmlElementAttribute(bankPayment.Name, bankPayment));

XmlAttributeOverrides overrides = new XmlAttributeOverrides();
overrides.Add(typeof(Order), "Payments", attributes);

The appropriate XmlSerializer constructor would then be used.

NOTE: by doesn't work I mean the InvalidOperationException (BankPayment was not expected...) is thrown.

Can anyone shed some light on the subject? How would one go about and debug the issue further?

Community
  • 1
  • 1
lsoliveira
  • 4,560
  • 3
  • 20
  • 31

6 Answers6

111

This worked for me:

[XmlInclude(typeof(BankPayment))]
[Serializable]
public abstract class Payment { }    

[Serializable]
public class BankPayment : Payment {} 

[Serializable]
public class Payments : List<Payment>{}

XmlSerializer serializer = new XmlSerializer(typeof(Payments), new Type[]{typeof(Payment)});
C.J.
  • 15,637
  • 9
  • 61
  • 77
bizl
  • 1,535
  • 2
  • 12
  • 21
  • 25
    So the base type needs to know all of its implementations? This doesn't seem like a very good solution. Is there no other way? – Alexander Stolz Jul 03 '14 at 08:10
  • 3
    @AlexanderStolz for generic implementation passing new Type while creating XmlSerializable Object is best solution. As mentioned http://stackoverflow.com/a/2689660/698127 – Aamol Jul 29 '15 at 01:23
  • The `[Serializable]` attribute is not required though. You only need to specify what types are expected to be serialized by the name of the abstract class - with the `XmlInclude` attribute. – SeReGa Sep 22 '21 at 00:03
44

Just solved the issue. After digging around for a while longer, I found this SO post which covers the exact same situation. It got me in the right track.

Basically, the XmlSerializer needs to know the default namespace if derived classes are included as extra types. The exact reason why this has to happen is still unknown but, still, serialization is working now.

Community
  • 1
  • 1
lsoliveira
  • 4,560
  • 3
  • 20
  • 31
5

Base on this I was able to solve this by changing the constructor of XmlSerializer I was using instead of changing the classes.

Instead of using something like this (suggested in the other answers):

[XmlInclude(typeof(Derived))]
public class Base {}

public class Derived : Base {}

public void Serialize()
{
    TextWriter writer = new StreamWriter(SchedulePath);
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(List<Derived>));
    xmlSerializer.Serialize(writer, data);
    writer.Close();
}

I did this:

public class Base {}

public class Derived : Base {}

public void Serialize()
{
    TextWriter writer = new StreamWriter(SchedulePath);
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(List<Derived>), new[] { typeof(Derived) });
    xmlSerializer.Serialize(writer, data);
    writer.Close();
}
derekantrican
  • 1,891
  • 3
  • 27
  • 57
3

Just do it in the Base, that way any child can be Serialized, less code cleaner code.

public abstract class XmlBaseClass  
{
  public virtual string Serialize()
  {
    this.SerializeValidation();

    XmlSerializerNamespaces XmlNamespaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
    XmlWriterSettings XmlSettings = new XmlWriterSettings
    {
      Indent = true,
      OmitXmlDeclaration = true
    };

    StringWriter StringWriter = new StringWriter();

    XmlSerializer Serializer = new XmlSerializer(this.GetType());
    XmlWriter XmlWriter = XmlWriter.Create(StringWriter, XmlSettings);
    Serializer.Serialize(XmlWriter, this, XmlNamespaces);
    StringWriter.Flush();
    StringWriter.Close();

    return StringWriter.ToString();

  }

  protected virtual void SerializeValidation() {}
}

[XmlRoot(ElementName = "MyRoot", Namespace = "MyNamespace")]
public class XmlChildClass : XmlBaseClass
{
  protected override void SerializeValidation()
  {
    //Add custom validation logic here or anything else you need to do
  }
}

This way you can call Serialize on the child class no matter the circumstance and still be able to do what you need to before object Serializes.

A. Dady
  • 143
  • 1
  • 8
  • I would like to highlight the code line which is important to visualize the way we can avoid XmlInclude or other attributions: `XmlSerializer Serializer = new XmlSerializer(this.GetType());` – vikas pachisia Dec 08 '21 at 13:04
2

I agree with bizl

[XmlInclude(typeof(ParentOfTheItem))]
[Serializable]
public abstract class WarningsType{ }

also if you need to apply this included class to an object item you can do like that

[System.Xml.Serialization.XmlElementAttribute("Warnings", typeof(WarningsType))]
public object[] Items
{
    get
    {
        return this.itemsField;
    }
    set
    {
        this.itemsField = value;
    }
}
Hamit YILDIRIM
  • 4,224
  • 1
  • 32
  • 35
0

I added this [System.Xml.Serialization.XmlAttributeAttribute()] for property, its resolved for me.

Suraj Rao
  • 29,388
  • 11
  • 94
  • 103