Verwenden Sie das Attribut XmlInclude oder SoapInclude, um Typen anzugeben, die statisch nicht bekannt sind

95

Ich habe ein sehr seltsames Problem bei der Arbeit mit .NETs XmlSerializer.

Nehmen Sie die folgenden Beispielklassen:

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, es gibt drei verschiedene Methoden, um das Problem zu lösen InvalidOperationException, das dadurch verursacht wird, dass der Serializer die abgeleiteten Typen von nicht kennt Payment.

1. Hinzufügen XmlIncludezur PaymentKlassendefinition:

Dies ist nicht möglich, da alle Klassen als externe Referenzen enthalten sind, über die ich keine Kontrolle habe.

2. Übergeben der Typen der abgeleiteten Typen während der Erstellung der XmlSerializerInstanz

Funktioniert nicht

3. Definieren XmlAttributeOverridesder Zieleigenschaft, um die Standardserialisierung der Eigenschaft zu überschreiben (wie in diesem SO-Beitrag erläutert ).

Funktioniert auch nicht ( XmlAttributeOverridesInitialisierung folgt).

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);

Der entsprechende XmlSerializerKonstruktor würde dann verwendet.

HINWEIS: Mit funktioniert nicht Ich meine, das InvalidOperationException( BankPaymentwurde nicht erwartet ... ) wird geworfen.

Kann jemand etwas Licht in das Thema bringen? Wie würde man vorgehen und das Problem weiter debuggen?

lsoliveira
quelle

Antworten:

91

Das hat bei mir funktioniert:

[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)});
bizl
quelle
15
Der Basistyp muss also alle seine Implementierungen kennen? Dies scheint keine sehr gute Lösung zu sein. Gibt es keinen anderen Weg?
Alexander Stolz
2
@AlexanderStolz für die generische Implementierung, bei der beim Erstellen eines XmlSerializable-Objekts ein neuer Typ übergeben wird, ist die beste Lösung. Wie bereits erwähnt stackoverflow.com/a/2689660/698127
Aamol
39

Habe gerade das Problem gelöst. Nachdem ich eine Weile länger herumgegraben hatte, fand ich diesen SO-Beitrag , der genau die gleiche Situation abdeckt. Es hat mich auf den richtigen Weg gebracht.

Grundsätzlich XmlSerializermuss der Standard-Namespace bekannt sein, wenn abgeleitete Klassen als zusätzliche Typen enthalten sind. Der genaue Grund, warum dies geschehen muss, ist noch unbekannt, aber die Serialisierung funktioniert derzeit noch.

lsoliveira
quelle
2

Ich stimme bizl zu

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

Auch wenn Sie diese enthaltene Klasse auf ein Objektelement anwenden müssen, können Sie dies tun

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

Tun Sie es einfach in der Basis, auf diese Weise kann jedes Kind serialisiert werden, weniger 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
  }
}

Auf diese Weise können Sie Serialize für die untergeordnete Klasse aufrufen, unabhängig von den Umständen, und trotzdem das tun, was Sie benötigen, bevor das Objekt Serializes ausgeführt wird.

A. Dady
quelle
0

Auf dieser Grundlage konnte ich dieses Problem lösen, indem XmlSerializerich den von mir verwendeten Konstruktor änderte , anstatt die Klassen zu ändern.

Anstatt so etwas zu verwenden (in den anderen Antworten vorgeschlagen):

[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();
}

Ich war das:

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
quelle