2017-02-20 2 views
2

J'ai récemment enregistré des gestionnaires d'événements pour des nœuds inconnus, des éléments et des attributs avec le XMLSerializer que j'utilise pour désérialiser des types complexes d'une hiérarchie de types. Je l'ai fait parce qu'une partie du XML que je reçois provient de tiers; Je suis intéressé par les changements de format de données qui pourraient causer des problèmes de ma part.XMLSerializer met en garde contre des nœuds/attributs inconnus lors de la désérialisation de types dérivés

Dans XML, le XMLSerializer génère l'attribut XML standard xsi:type="somederivedtypename" pour identifier le type dérivé réel représenté par un élément XML.

J'ai été surpris de voir que le même sérialiseur traite ce même attribut qu'il vient de produire comme inconnu lors de la désérialisation. Fait intéressant cependant, la désérialisation est correcte et complète (également avec des types plus complexes et des données dans mon programme du monde réel). Cela signifie que le sérialiseur évalue correctement les informations de type au début de la désérialisation. Mais lors d'une étape ultérieure d'extraction de données, l'attribut est apparemment confondu avec une partie de données vraie de l'objet, ce qui est bien sûr inconnu.

Dans mon application, les avertissements gratuits finissent par encombrer un fichier journal à usage général qui n'est pas souhaité. À mon avis, le sérialiseur devrait relire le XML produit sans hoquet. Mes questions:

  • Est-ce que je fais quelque chose de mal?
  • Y a-t-il une solution de contournement?

Un exemple minimal est ici:

using System; 
using System.IO; 
using System.Xml.Serialization; 

namespace XsiTypeAnomaly 
{ 
    /// <summary> 
    /// A trivial base type. 
    /// </summary> 
    [XmlInclude(typeof(DerivedT))] 
    public class BaseT{} 

    /// <summary> 
    /// A trivial derived type to demonstrate a serialization issue. 
    /// </summary> 
    public class DerivedT : BaseT 
    { 
     public int anInt { get; set; } 
    } 

    class Program 
    { 
     private static void serializer_UnknownAttribute 
      ( object sender, 
       XmlAttributeEventArgs e) 
     { 
      Console.Error.WriteLine("Warning: Deserializing " 
        + e.ObjectBeingDeserialized 
        + ": Unknown attribute " 
        + e.Attr.Name); 
       } 

     private static void serializer_UnknownNode(object sender, XmlNodeEventArgs e) 
     { 
      Console.Error.WriteLine("Warning: Deserializing " 
        + e.ObjectBeingDeserialized 
        + ": Unknown node " 
        + e.Name); 
     } 

     private static void serializer_UnknownElement(object sender, XmlElementEventArgs e) 
     { 
      Console.Error.WriteLine("Warning: Deserializing " 
        + e.ObjectBeingDeserialized 
        + ": Unknown element " 
        + e.Element.Name); 
     } 

     /// <summary> 
     /// Serialize, display the xml, and deserialize a trivial object. 
     /// </summary> 
     /// <param name="args"></param> 
     static void Main(string[] args) 
     { 
      BaseT aTypeObj = new DerivedT() { anInt = 1 }; 
      using (MemoryStream stream = new MemoryStream()) 
      { 
       var serializer = new XmlSerializer(typeof(BaseT)); 

       // register event handlers for unknown XML bits 
       serializer.UnknownAttribute += serializer_UnknownAttribute; 
       serializer.UnknownElement += serializer_UnknownElement; 
       serializer.UnknownNode += serializer_UnknownNode; 

       serializer.Serialize(stream, aTypeObj); 
       stream.Flush(); 

       // output the xml 
       stream.Position = 0; 
       Console.Write((new StreamReader(stream)).ReadToEnd() + Environment.NewLine); 
       stream.Position = 0; 
       var serResult = serializer.Deserialize(stream) as DerivedT; 

       Console.WriteLine(
         (serResult.anInt == 1 ? "Successfully " : "Unsuccessfully ") 
        + "read back object"); 
      } 
     } 
    } 
} 

Sortie:

<?xml version="1.0"?> 
<BaseT xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="DerivedT"> 
    <anInt>1</anInt> 
</BaseT> 
Warning: Deserializing XsiTypeAnomaly.DerivedT: Unknown node xsi:type 
Warning: Deserializing XsiTypeAnomaly.DerivedT: Unknown attribute xsi:type 
Successfully read back object 
+0

L'avertissement semble avoir du sens car vous créez un sérialiseur pour 'BaseT', puis vous alimente en réalité un objet' DerivedT'. Si vous venez de créer un sérialiseur pour 'DerivedT', l'avertissement disparaît. – jsanalytics

+1

@jstreet Mais le but de cet attribut est de permettre au sérialiseur de la classe de base de désérialiser les objets dérivés. Imaginez une liste de base, qui peut contenir n'importe quel type dérivé. Le code appelant ne sait pas et ne se soucie pas quels types dérivés réels sont détenus dans la liste. Le code de gestion de liste a été réellement écrit avant que beaucoup de types dérivés aient existé. –

+2

Je vois votre point, et le sérialiseur est capable de désérialiser l'objet dérivé, avec "l'incovenience" de l'avertissement, parce qu'il ne connaît pas la propriété 'anInt'. Suggestion: lorsque vous créez votre sérialiseur, utilisez 'aTypeObj.GetType()' au lieu d'utiliser un type explicite, base ou dérivé. – jsanalytics

Répondre

4

Est-ce que je fais quelque chose de mal?

Je ne pense pas. Je partage votre avis que XmlSerializer devrait désérialiser sa propre sortie sans aucun avertissement. En outre, xsi:type est un attribut standard défini dans le XML Schema specification et, évidemment, il est pris en charge par XmlSerializer, comme démontré par votre exemple et documenté dans MSDN Library.

Par conséquent, ce comportement ressemble simplement à un oubli. Je peux imaginer un groupe de développeurs Microsoft travaillant sur différents aspects de XmlSerializer pendant le développement du .NET Framework, et ne jamais tester xsi:type et les événements en même temps.

Cela signifie que le sérialiseur évalue correctement les informations de type au cours d'une étape précoce de la désérialisation. Mais lors d'une étape ultérieure d'extraction de données, l'attribut est apparemment confondu avec une partie de données vraie de l'objet, ce qui est bien sûr inconnu.

Votre observation est correcte.

La classe XmlSerializer génère un assembly dynamique pour sérialiser et désérialiser les objets. Dans votre exemple, la méthode générée que désérialise cas de DerivedT ressemble à quelque chose comme ceci:

private DerivedT Read2_DerivedT(bool isNullable, bool checkType) 
{ 
    // [Code that uses isNullable and checkType omitted...] 

    DerivedT derivedT = new DerivedT(); 
    while (this.Reader.MoveToNextAttribute()) 
    { 
     if (!this.IsXmlnsAttribute(this.Reader.Name)) 
      this.UnknownNode(derivedT); 
    } 

    this.Reader.MoveToElement(); 
    // [Code that reads child elements and populates derivedT.anInt omitted...] 
    return derivedT; 
} 

Le désérialiseur appelle cette méthode après lit l'attribut xsi:type et décide de créer une instance de DerivedT. Comme vous pouvez le voir, la boucle while déclenche l'événement UnknownNode pour tous les attributs sauf les attributs xmlns. C'est pourquoi vous obtenez l'événement UnknownNode (et UnknownAttribute) pour xsi:type. La boucle while est générée par la méthode XmlSerializationReaderILGen.WriteAttributes interne. Le code est plutôt compliqué, mais je ne vois pas de chemin de code qui ferait que les attributs xsi:type soient ignorés (autre que la deuxième solution de contournement décrite ci-dessous).

Y a-t-il une solution de contournement?

Je voudrais juste ignorer les événements UnknownNode et UnknownAttribute pour xsi:type:

private static void serializer_UnknownNode(object sender, XmlNodeEventArgs e) 
{ 
    if (e.NodeType == XmlNodeType.Attribute && 
     e.NamespaceURI == XmlSchema.InstanceNamespace && e.LocalName == "type") 
    { 
     // Ignore xsi:type attributes. 
    } 
    else 
    { 
     // [Log it...] 
    } 
} 

// [And similarly for UnknownAttribute using e.Attr instead of e...] 

Une autre (hackier, OMI) solution consiste à la carte xsi:type à une propriété fictive dans la classe BaseT:

[XmlInclude(typeof(DerivedT))] 
public class BaseT 
{ 
    [XmlAttribute("type", Namespace = XmlSchema.InstanceNamespace)] 
    [DebuggerBrowsable(DebuggerBrowsableState.Never)] // Hide this useless property 
    public string XmlSchemaType 
    { 
     get { return null; } // Must return null for XmlSerializer.Serialize to work 
     set { } 
    } 
} 
+0

Juste curieux: Avez-vous démonté l'ensemble généré? Comment? –

+0

Oui, je l'ai fait. [Cette réponse] (http://stackoverflow.com/a/3302936/1127114) explique comment faire en sorte que XmlSerializer conserve l'assembly généré. J'ai utilisé JetBrains dotPeek pour démonter le résultat. –

0

Je ne pense pas que ce soit la bonne façon d'utiliser XmlSerializer, même si vous avez la bonne sortie avec le avertissements, dans un scénario plus avancé, on ne sait pas ce qui pourrait mal tourner.

Vous devez utiliser le type dérivé réel (aTypeObj.GetType()) ou même Generics pour obtenir ce tri.

+0

Avez-vous la documentation pour soutenir cette déclaration? –

+0

Je ne suis pas vraiment, je dis juste que c'est comme ça que je le fais, toutes mes routines de sérialisation sont définies dans helpers et sont réutilisées dans toute l'application, en utilisant des génériques ou .GetType() –

+2

Le point est, il est impossible de connaître le type dérivé spécifique de l'objet (notez que, après la réponse de l'utilisateur1892538, j'ai vérifié que les événements sont également levés pour les membres, pas seulement pour les racines XML). Et puis [Jon Skeet semble dire] (http://stackoverflow.com/a/32368306/6996876) Je devrais faire exactement ce que je fais, donc je suppose que j'utilise correctement le sérialiseur. –

0

Avez-vous essayé le constructeur XMLSerializer où vous pouvez passer le type dérivé comme l'un des extraTypes?

Regardez ici: https://msdn.microsoft.com/en-us/library/e5aakyae%28v=vs.110%29.aspx

Vous pouvez l'utiliser comme ceci:

var serializer = new XmlSerializer(typeof(BaseT), new Type[] { typeof(DerivedT) }); 

Bien sûr, en général, vous pouvez récupérer la liste des types dervied d'ailleurs. Par exemple d'un autre assemblage.