web-dev-qa-db-fra.com

Y a-t-il une raison pour laquelle une classe de base décorée avec XmlInclude lèverait toujours une exception de type inconnu lorsqu'elle est sérialisée?

Je vais simplifier le code pour économiser de l'espace mais ce qui est présenté illustre le problème principal.

J'ai une classe qui a une propriété qui est un type de base. Il existe 3 classes dérivées qui pourraient être affectées à cette propriété.

Si j'affecte l'une des classes dérivées au conteneur et tente de sérialiser le conteneur, le XmlSerializer lance le redouté:

"Le type x n'était pas prévu. Utilisez l'attribut XmlInclude ou SoapInclude pour spécifier des types qui ne sont pas connus statiquement."

Cependant, ma classe de base est déjà décorée avec cet attribut, donc je pense qu'il doit y avoir une exigence supplémentaire "cachée".

La partie vraiment étrange est que le sérialiseur WCF par défaut n'a aucun problème avec cette hiérarchie de classes.

La classe Container

[DataContract]
[XmlRoot(ElementName = "TRANSACTION", Namespace = Constants.Namespace)]
public class PaymentSummaryRequest : CommandRequest
{
    [DataMember]
    public PaymentSummary Summary { get; set; }

    public PaymentSummaryRequest()
    {
        Mechanism = CommandMechanism.PaymentSummary;
    }
}

La classe de base

[DataContract]
[XmlInclude(typeof(xPaymentSummary))]
[XmlInclude(typeof(yPaymentSummary))]
[XmlInclude(typeof(zPaymentSummary))]
[KnownType(typeof(xPaymentSummary))]
[KnownType(typeof(yPaymentSummary))]
[KnownType(typeof(zPaymentSummary))]
public abstract class PaymentSummary
{     
}

ne des classes dérivées

[DataContract]
public class xPaymentSummary : PaymentSummary
{
}

Le code de sérialisation

var serializer = new XmlSerializer(typeof(PaymentSummaryRequest));
serializer.Serialize(Console.Out,new PaymentSummaryRequest{Summary = new xPaymentSummary{}});

L'exception

System.InvalidOperationException: une erreur s'est produite lors de la génération du document XML. ---> System.InvalidOperationException: le type xPaymentSummary n'était pas attendu. Utilisez l'attribut XmlInclude ou SoapInclude pour spécifier des types qui ne sont pas connus statiquement. à

Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterPaymentSummaryRequest.Write13_PaymentSummary (String n, String ns, PaymentSummary o, Boolean isNullable, Boolean needType) at

Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterPaymentSummaryRequest.Write14_PaymentSummaryRequest (String n, String ns, PaymentSummaryRequest o, Boolean isNullable, Boolean needType) at

Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriterPaymentSummaryRequest.Write15_TRANSACTION (Object o) --- Fin de la trace de la pile des exceptions internes --- à

System.Xml.Serialization.XmlSerializer.Serialize (XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id) at

System.Xml.Serialization.XmlSerializer.Serialize (TextWriter textWriter, Object o, XmlSerializerNamespaces namespaces)
sur UserQuery.RunUserAuthoredQuery () dans c:\Users\Tedford\AppData\Local\Temp\uqacncyo.0.cs: ligne 47

42
Tedford

Le problème que vous voyez est dû au fait que PaymentSummaryRequest définit l'espace de noms. Vous pouvez soit supprimer l'espace de noms, soit ajouter un espace de noms à la classe PaymentSummary:

[XmlRoot(Namespace = Constants.Namespace)]
[XmlInclude(typeof(xxxPaymentSummary))]
public abstract class PaymentSummary
{
}

Comme @Tedford le mentionne dans son commentaire ci-dessous, l'espace de noms n'est requis que lors de l'utilisation de types dérivés.

Il semble que lors de la génération de l'assembly de sérialisation XML, puisque le nœud racine possède un espace de noms défini et que la classe de base ne le fait pas, il n'inclut pas la logique XML Include dans l'assembly de sérialisation généré.

Une autre approche pour résoudre le problème serait de supprimer les déclarations d'espace de noms des classes elles-mêmes et de spécifier l'espace de noms sur le constructeur XmlSerializer:

var serializer = new XmlSerializer(
    typeof(PaymentSummaryRequest), 
    Constants.Namespace
);
41
Randy Levy

J'ai eu le même problème et certains googleurs m'ont conduit ici. Je ne pouvais pas accepter d'avoir l'attribut XMLInclude pour chaque implémentation dans la classe de base. Je l'ai fait fonctionner avec une méthode de sérialisation générique. Tout d'abord, certains des exemples originaux de l'OP, mais légèrement modifiés pour plus de clarté:

public abstract class PaymentSummary
{
  public string _summary;
  public string _type;
}

public class xPaymentSummary : PaymentSummary
{
  public xPaymentSummary() { }

  public xPaymentSummary(string summary)
  {
     _summary = summary;
     _type = this.GetType().ToString();
  }
}

En plus de ce qui précède, il existe également un yPaymentSummary et zPaymentSummary avec exactement la même implémentation. Ceux-ci sont ajoutés à une collection, puis la méthode de sérialisation peut être appelée sur chacun:

List<PaymentSummary> summaries = new List<PaymentSummary>();
summaries.Add(new xPaymentSummary("My summary is X."));
summaries.Add(new yPaymentSummary("My summary is Y."));
summaries.Add(new zPaymentSummary("My summary is Z."));

foreach (PaymentSummary sum in summaries)
  SerializeRecord(sum);

Enfin, la méthode de sérialisation - une méthode générique simple, qui utilise le type d'enregistrement lors de la sérialisation:

static void SerializeRecord<T>(T record) where T: PaymentSummary
{
  var serializer = new XmlSerializer(record.GetType());
  serializer.Serialize(Console.Out, record);

  Console.WriteLine(" ");
  Console.WriteLine(" ");
}

Ce qui précède donne la sortie suivante:

enter image description here

0
RooiWillie