Des pièges de sérialisation XML .NET? [fermé]

121

J'ai rencontré quelques pièges lors de la sérialisation XML C # que je pensais partager:


using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;

[XmlRoot("dictionary")]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
{      
    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

        bool wasEmpty = reader.IsEmptyElement;
        reader.Read();

        if (wasEmpty)
            return;

        while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
        {
            reader.ReadStartElement("item");

            reader.ReadStartElement("key");
            TKey key = (TKey)keySerializer.Deserialize(reader);
            reader.ReadEndElement();

            reader.ReadStartElement("value");
            TValue value = (TValue)valueSerializer.Deserialize(reader);
            reader.ReadEndElement();

            this.Add(key, value);

            reader.ReadEndElement();
            reader.MoveToContent();
        }
        reader.ReadEndElement();
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

        foreach (TKey key in this.Keys)
        {
            writer.WriteStartElement("item");

            writer.WriteStartElement("key");
            keySerializer.Serialize(writer, key);
            writer.WriteEndElement();

            writer.WriteStartElement("value");
            TValue value = this[key];
            valueSerializer.Serialize(writer, value);
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
    }
}

Y a-t-il d'autres pièges de sérialisation XML?

kurious
la source
Cherchez plus de pièges lol, vous pourrez peut-être m'aider: stackoverflow.com/questions/2663836/…
Shimmy Weitzhandler
1
De plus, vous voudrez jeter un coup d'œil à l'implémentation du dictionnaire sérialisable par Charles Feduke, il a fait en sorte que l'écrivain XML remarque entre les membres attribuables aux membres réguliers à sérialiser par le sérialiseur par défaut: deploymentzone.com/2008/09/19/...
Shimmy Weitzhandler
Cela ne semble pas attraper tous les pièges. Je mets le IEqualityComparer dans le constructeur, mais cela n'est pas sérialisé dans ce code. Avez-vous des idées sur la façon d'étendre ce dictionnaire pour y inclure cette information? ces informations pourraient-elles être traitées via l'objet Type?
ColinCren

Réponses:

27

Un autre énorme problème: lors de la sortie de XML via une page Web (ASP.NET), vous ne voulez pas inclure la marque d'ordre d'octet Unicode . Bien entendu, les façons d'utiliser ou non la nomenclature sont presque les mêmes:

MAUVAIS (comprend la nomenclature):

XmlTextWriter wr = new XmlTextWriter(stream, new System.Text.Encoding.UTF8);

BIEN:

XmlTextWriter  wr = new XmlTextWriter(stream, new System.Text.UTF8Encoding(false))

Vous pouvez explicitement passer false pour indiquer que vous ne voulez pas la nomenclature. Notez la différence claire et évidente entre Encoding.UTF8et UTF8Encoding.

Les trois octets de nomenclature supplémentaires au début sont (0xEFBBBF) ou (239 187 191).

Référence: http://chrislaco.com/blog/troubleshooting-common-problems-with-the-xmlserializer/

Kalid
la source
4
Votre commentaire serait encore plus utile si vous nous disiez non seulement quoi, mais pourquoi.
Neil
1
Ce n'est pas vraiment lié à la sérialisation XML ... c'est juste un problème XmlTextWriter
Thomas Levesque
7
-1: pas lié à la question et vous ne devriez pas utiliser XmlTextWriterdans .NET 2.0 ou supérieur.
John Saunders
Lien de référence très utile. Merci.
Anil Vangari
21

Je ne peux pas encore faire de commentaires, donc je vais commenter le post de Dr8k et faire une autre observation. Les variables privées qui sont exposées en tant que propriétés publiques getter / setter, et qui sont sérialisées / désérialisées en tant que telles via ces propriétés. Nous l'avons fait à mon ancien travail à l'époque.

Une chose à noter cependant est que si vous avez une logique dans ces propriétés, la logique est exécutée, donc parfois, l'ordre de sérialisation compte réellement. Les membres sont implicitement classés en fonction de leur ordre dans le code, mais il n'y a aucune garantie, en particulier lorsque vous héritez d'un autre objet. Les commander explicitement est une douleur à l'arrière.

J'ai été brûlé par ça dans le passé.

Charles Graham
la source
17
J'ai trouvé cet article en cherchant des moyens de définir explicitement l'ordre des champs. Ceci est fait avec les attributs: [XmlElementAttribute (Order = 1)] public int Field {...} Inconvénient: l'attribut doit être spécifié pour TOUS les champs de la classe et tous ses descendants! IMO Vous devriez l'ajouter à votre message.
Cristian Diaconescu
15

Lors de la sérialisation dans une chaîne XML à partir d'un flux mémoire, veillez à utiliser MemoryStream # ToArray () au lieu de MemoryStream # GetBuffer () ou vous vous retrouverez avec des caractères indésirables qui ne seront pas désérialisés correctement (à cause du tampon supplémentaire alloué).

http://msdn.microsoft.com/en-us/library/system.io.memorystream.getbuffer(VS.80).aspx

realgt
la source
3
directement à partir de la documentation "Notez que le tampon contient des octets alloués qui peuvent être inutilisés. Par exemple, si la chaîne" test "est écrite dans l'objet MemoryStream, la longueur du tampon renvoyé par GetBuffer est de 256, et non de 4, avec 252 octets inutilisé. Pour obtenir uniquement les données de la mémoire tampon, utilisez la méthode ToArray. Cependant, ToArray crée une copie des données en mémoire. " msdn.microsoft.com/en-us/library/…
realgt
seulement maintenant vu cela. Cela ne sonne plus comme un non-sens.
John Saunders
Jamais entendu cela auparavant, ce qui est utile pour le débogage.
Ricky
10

Si le sérialiseur rencontre un membre / une propriété dont le type est une interface, il ne sérialisera pas. Par exemple, les éléments suivants ne seront pas sérialisés en XML:

public class ValuePair
{
    public ICompareable Value1 { get; set; }
    public ICompareable Value2 { get; set; }
}

Bien que cela sérialisera:

public class ValuePair
{
    public object Value1 { get; set; }
    public object Value2 { get; set; }
}
Allon Guralnek
la source
Si vous obtenez une exception avec un message "Type non résolu pour le membre ...", c'est peut-être ce qui se passe.
Kyle Krull
9

IEnumerables<T>qui sont générés via les rendements de rendement ne sont pas sérialisables. Cela est dû au fait que le compilateur génère une classe distincte pour implémenter le rendement et que cette classe n'est pas marquée comme sérialisable.

abatishchev
la source
Ceci s'applique à la sérialisation «autre», c'est-à-dire à l'attribut [Serializable]. Cela ne fonctionne pas non plus pour XmlSerializer.
Tim Robinson
8

Vous ne pouvez pas sérialiser les propriétés en lecture seule. Vous devez avoir un getter et un setter, même si vous n'avez jamais l'intention d'utiliser la désérialisation pour transformer XML en objet.

Pour la même raison, vous ne pouvez pas sérialiser les propriétés qui renvoient des interfaces: le désérialiseur ne saurait pas quelle classe concrète instancier.

Tim Robinson
la source
1
En fait, vous pouvez sérialiser une propriété de collection même si elle n'a pas de setter, mais elle doit être initialisée dans le constructeur pour que la désérialisation puisse y ajouter des éléments
Thomas Levesque
7

Oh, en voici une bonne: puisque le code de sérialisation XML est généré et placé dans une DLL distincte, vous n'obtenez aucune erreur significative lorsqu'il y a une erreur dans votre code qui brise le sérialiseur. Juste quelque chose comme "impossible de localiser s3d3fsdf.dll". Agréable.

Eric Z Beard
la source
11
Vous pouvez générer cette DLL à l'avance à l'aide de XML "Serializer Generator Tool (Sgen.exe)" et la déployer avec votre application.
huseyint
6

Impossible de sérialiser un objet qui n'a pas de constructeur sans paramètre (juste mordu par celui-là).

Et pour une raison quelconque, à partir des propriétés suivantes, Value est sérialisé, mais pas FullName:

    public string FullName { get; set; }
    public double Value { get; set; }

Je n'ai jamais compris pourquoi, j'ai juste changé la valeur en interne ...

Benjol
la source
4
Le constructeur sans paramètre peut être privé / protégé. Ce sera suffisant pour le sérialiseur XML. Le problème avec FullName est vraiment étrange, ne devrait pas arriver ...
Max Galkin
@Yacoder: Peut-être parce que pas double?mais juste double?
abatishchev
FullName était probablement nullet ne générera donc pas de XML une fois sérialisé
Jesper
4

Si votre assembly généré par sérialisation XML n'est pas dans le même contexte de chargement que le code qui tente de l'utiliser, vous rencontrerez des erreurs impressionnantes telles que:

System.InvalidOperationException: There was an error generating the XML document.
---System.InvalidCastException: Unable to cast object
of type 'MyNamespace.Settings' to type 'MyNamespace.Settings'. at
Microsoft.Xml.Serialization.GeneratedAssembly.
  XmlSerializationWriterSettings.Write3_Settings(Object o)

La cause de cela pour moi était un plugin chargé à l'aide du contexte LoadFrom qui présente de nombreux inconvénients par rapport à l'utilisation du contexte Load. C'est un peu amusant de suivre celui-là.

user7116
la source
4

Si vous essayez de sérialiser un tableau, List<T>ou IEnumerable<T>qui contient des instances de sous - classes de T, vous devez utiliser XmlArrayItemAttribute pour répertorier tous les sous-types utilisés. Sinon, vous obtiendrez un message inutile System.InvalidOperationExceptionlors de l'exécution lors de la sérialisation.

Voici une partie d'un exemple complet de la documentation

public class Group
{  
   /* The XmlArrayItemAttribute allows the XmlSerializer to insert both the base 
      type (Employee) and derived type (Manager) into serialized arrays. */

   [XmlArrayItem(typeof(Manager)), XmlArrayItem(typeof(Employee))]
   public Employee[] Employees;
MarkJ
la source
3

Les variables / propriétés privées ne sont pas sérialisées dans le mécanisme par défaut pour la sérialisation XML, mais sont dans la sérialisation binaire.

Charles Graham
la source
2
Oui, si vous utilisez la sérialisation XML "par défaut". Vous pouvez spécifier une logique de sérialisation XML personnalisée implémentant IXmlSerializable dans votre classe et sérialiser tous les champs privés dont vous avez besoin / souhaitez.
Max Galkin
1
Eh bien, c'est vrai. Je vais éditer ceci. Mais mettre en œuvre cette interface est une sorte de douleur dans le cul d'après ce dont je me souviens.
Charles Graham
3

Les propriétés marquées avec l' Obsoleteattribut ne sont pas sérialisées. Je n'ai pas testé avec l' Deprecatedattribut mais je suppose que cela agirait de la même manière.

James Hulse
la source
2

Je ne peux pas vraiment expliquer celui-ci, mais j'ai trouvé que cela ne sérialisera pas:

[XmlElement("item")]
public myClass[] item
{
    get { return this.privateList.ToArray(); }
}

mais cela va:

[XmlElement("item")]
public List<myClass> item
{
    get { return this.privateList; }
}

Et il convient également de noter que si vous sérialisez vers un memstream, vous voudrez peut-être rechercher 0 avant de l'utiliser.

Annakata
la source
Je pense que c'est parce qu'il ne peut pas le reconstruire. Dans le second exemple, il peut appeler item.Add () pour ajouter des éléments à la liste. Il ne peut pas le faire dans le premier.
ilitirit le
18
Utilisez: [XmlArray ("item"), XmlArrayItem ("myClass", typeof (myClass))]
RvdK
1
bravo pour ça! apprenez quelque chose tous les jours
annakata
2

Soyez prudent en sérialisant les types sans sérialisation explicite, cela peut entraîner des retards pendant que .Net les construit. J'ai découvert cela récemment en sérialisant les paramètres RSAP .

Keith
la source
2

Si votre XSD utilise des groupes de substitution, il est probable que vous ne puissiez pas (dé) sérialiser automatiquement. Vous devrez écrire vos propres sérialiseurs pour gérer ce scénario.

Par exemple.

<xs:complexType name="MessageType" abstract="true">
    <xs:attributeGroup ref="commonMessageAttributes"/>
</xs:complexType>

<xs:element name="Message" type="MessageType"/>

<xs:element name="Envelope">
    <xs:complexType mixed="false">
        <xs:complexContent mixed="false">
            <xs:element ref="Message" minOccurs="0" maxOccurs="unbounded"/>
        </xs:complexContent>
    </xs:complexType>
</xs:element>

<xs:element name="ExampleMessageA" substitutionGroup="Message">
    <xs:complexType mixed="false">
        <xs:complexContent mixed="false">
                <xs:attribute name="messageCode"/>
        </xs:complexContent>
    </xs:complexType>
</xs:element>

<xs:element name="ExampleMessageB" substitutionGroup="Message">
    <xs:complexType mixed="false">
        <xs:complexContent mixed="false">
                <xs:attribute name="messageCode"/>
        </xs:complexContent>
    </xs:complexType>
</xs:element>

Dans cet exemple, une enveloppe peut contenir des messages. Cependant, le sérialiseur par défaut du .NET ne fait pas la distinction entre Message, ExampleMessageA et ExampleMessageB. Il ne sérialisera que vers et depuis la classe Message de base.

ilitirit
la source
0

Les variables / propriétés privées ne sont pas sérialisées dans la sérialisation XML, mais sont dans la sérialisation binaire.

Je pense que cela vous permet également si vous exposez les membres privés via des propriétés publiques - les membres privés ne sont pas sérialisés, de sorte que les membres publics font tous référence à des valeurs nulles.

Dr8k
la source
Ce n'est pas vrai. Le setter de la propriété publique serait appelé et définirait vraisemblablement le membre privé.
John Saunders