Utilizzare l'attributo XmlInclude o SoapInclude per specificare i tipi che non sono noti staticamente


97

Ho un problema molto strano quando lavoro con .NET XmlSerializer.

Prendi le seguenti classi di esempio:

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
}

Per quanto ne so, ci sono tre diversi metodi per risolvere il problema InvalidOperationExceptioncausato dal serializzatore che non conosce i tipi derivati ​​di Payment.

1. Aggiunta XmlIncludealla Paymentdefinizione della classe:

Ciò non è possibile perché tutte le classi sono incluse come riferimenti esterni su cui non ho alcun controllo.

2. Passaggio dei tipi di tipi derivati ​​durante la creazione XmlSerializerdell'istanza

Non funziona

3. Definizione XmlAttributeOverridesdella proprietà di destinazione per sovrascrivere la serializzazione predefinita della proprietà (come spiegato in questo post SO )

Inoltre non funziona ( XmlAttributeOverridessegue l'inizializzazione).

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

Verrà XmlSerializerquindi utilizzato il costruttore appropriato .

NOTA: per non funziona intendo che InvalidOperationException( BankPaymentnon era previsto ... ) viene lanciato.

Qualcuno può far luce sull'argomento? Come si potrebbe procedere ed eseguire il debug ulteriormente del problema?

Risposte:


92

Questo ha funzionato per 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)});

15
Quindi il tipo di base deve conoscere tutte le sue implementazioni? Questa non sembra una buona soluzione. Non c'è altro modo?
Alexander Stolz

2
@AlexanderStolz per l'implementazione generica che passa il nuovo tipo durante la creazione dell'oggetto XmlSerializable è la soluzione migliore. Come accennato stackoverflow.com/a/2689660/698127
Aamol

39

Ho appena risolto il problema. Dopo aver scavato un po 'più a lungo, ho trovato questo post SO che copre esattamente la stessa situazione. Mi ha messo sulla strada giusta.

Fondamentalmente, è XmlSerializernecessario conoscere lo spazio dei nomi predefinito se le classi derivate sono incluse come tipi extra. Il motivo esatto per cui ciò deve accadere è ancora sconosciuto ma, ancora, la serializzazione ora funziona.


2

Sono d'accordo con bizl

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

inoltre, se hai bisogno di applicare questa classe inclusa a un oggetto, puoi farlo

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

1

Basta farlo nella Base, in questo modo qualsiasi bambino può essere serializzato, meno codice per la pulizia del codice.

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
  }
}

In questo modo puoi chiamare Serialize sulla classe figlia indipendentemente dalle circostanze ed essere comunque in grado di fare ciò di cui hai bisogno prima che l'oggetto Serializzi.


0

Sulla base di questo sono stato in grado di risolvere questo problema cambiando il costruttore XmlSerializerche stavo usando invece di cambiare le classi.

Invece di usare qualcosa di simile (suggerito nelle altre risposte):

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

L'ho fatto:

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();
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.