Utilisez l'attribut XmlInclude ou SoapInclude pour spécifier des types qui ne sont pas connus statiquement

98

J'ai un problème très étrange lorsque je travaille avec .NET XmlSerializer.

Prenez les exemples de classes suivants:

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, il existe trois méthodes différentes pour résoudre le problème InvalidOperationExceptioncausé par le sérialiseur ne sachant pas les types dérivés de Payment.

1. Ajout XmlIncludeà la Paymentdéfinition de classe:

Cela n'est pas possible car toutes les classes sont incluses en tant que références externes sur lesquelles je n'ai aucun contrôle.

2. Passer les types des types dérivés lors de la création de l' XmlSerializerinstance

Ça ne marche pas.

3. Définition XmlAttributeOverridesde la propriété cible afin de remplacer la sérialisation par défaut de la propriété (comme expliqué dans cet article SO )

Ne fonctionne pas non plus (l' XmlAttributeOverridesinitialisation suit).

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

Le XmlSerializerconstructeur approprié serait alors utilisé.

REMARQUE: par ne fonctionne pas, je veux dire que le InvalidOperationException( BankPaymentn'était pas prévu ... ) est lancé.

Quelqu'un peut-il faire la lumière sur le sujet? Comment procéder et déboguer davantage le problème?

lsoliveira
la source

Réponses:

93

Cela a fonctionné pour moi:

[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
la source
15
Le type de base a donc besoin de connaître toutes ses implémentations? Cela ne semble pas être une très bonne solution. N'y a-t-il pas d'autre moyen?
Alexander Stolz
2
@AlexanderStolz pour une implémentation générique passant un nouveau type lors de la création d'un objet XmlSerializable est la meilleure solution. Comme mentionné stackoverflow.com/a/2689660/698127
Aamol
39

Je viens de résoudre le problème. Après avoir fouillé un peu plus longtemps, j'ai trouvé ce message SO qui couvre exactement la même situation. Cela m'a mis sur la bonne voie.

Fondamentalement, il est XmlSerializernécessaire de connaître l'espace de noms par défaut si les classes dérivées sont incluses en tant que types supplémentaires. La raison exacte pour laquelle cela doit se produire est encore inconnue mais, toujours, la sérialisation fonctionne maintenant.

lsoliveira
la source
2

Je suis d'accord avec bizl

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

aussi si vous devez appliquer cette classe incluse à un objet objet, vous pouvez faire comme ça

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

Faites-le simplement dans la base, de cette façon tout enfant peut être sérialisé, moins de code plus propre.

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

De cette façon, vous pouvez appeler Serialize sur la classe enfant quelle que soit la circonstance et toujours pouvoir faire ce dont vous avez besoin avant que l'objet ne soit sérialisé.

A. Papa
la source
0

Sur cette base, j'ai pu résoudre ce problème en changeant le constructeur que XmlSerializerj'utilisais au lieu de changer les classes.

Au lieu d'utiliser quelque chose comme ça (suggéré dans les autres réponses):

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

J'ai fait ça:

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
la source