web-dev-qa-db-fra.com

Problèmes de sérialisation XML .NET?

J'ai rencontré quelques pièges lors de la sérialisation C # XML 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 problèmes de sérialisation XML?

120
Kalid

Autre énorme problème: lors de la sortie XML via une page Web (ASP.NET), vous ne voulez pas inclure le nicode Byte-Order Mark . Bien sûr, les façons d'utiliser ou de ne pas utiliser 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. Remarquez la différence claire et évidente entre Encoding.UTF8 et 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/

27
Kalid

Je ne peux pas encore faire de commentaires, je vais donc commenter le message de Dr8k et faire une autre observation. Variables privées qui sont exposées en tant que propriétés getter/setter publiques 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 emploi à 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 est réellement important. Les membres sont implicitement classés par la façon dont ils sont classés dans le code, mais il n'y a aucune garantie, surtout 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é.

21
Charles Graham

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

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

15
realgt

IEnumerables<T> générés via les rendements ne sont pas sérialisables. En effet, le compilateur génère une classe distincte pour implémenter return return et cette classe n'est pas marquée comme sérialisable.

10
bwillard

Si le sérialiseur rencontre un membre/une propriété qui a une interface comme type, il ne sera pas sérialisé. 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érialise:

public class ValuePair
{
    public object Value1 { get; set; }
    public object Value2 { get; set; }
}
10
Allon Guralnek

Vous ne pouvez pas sérialiser des 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 des propriétés qui renvoient des interfaces: le désérialiseur ne saurait pas quelle classe concrète instancier.

8
Tim Robinson

Oh, 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 rompt le sérialiseur. Juste quelque chose comme "impossible de localiser s3d3fsdf.dll". Agréable.

7
Eric Z Beard

Impossible de sérialiser un objet qui n'a pas de constructeur sans paramètre (vient d'être mordu par celui-ci).

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 réussi à comprendre pourquoi, je viens de changer la valeur en interne ...

6
Benjol

Une dernière chose à noter: vous ne pouvez pas sérialiser des membres de classe privés/protégés si vous utilisez la sérialisation XML "par défaut".

Mais 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.

http://msdn.Microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.aspx

5
Max Galkin

Vous pouvez rencontrer des problèmes de sérialisation d'objets de type Couleur et/ou Police.

Voici les conseils qui m'ont aidé:

http://www.codeproject.com/KB/XML/xmlsettings.aspx

http://www.codeproject.com/KB/cs/GenericXmlSerializition.aspx

4
Max Galkin

Voir " Prise en charge de la liaison des attributs du langage de définition de schéma XML avancé " pour plus de détails sur ce qui est pris en charge par le sérialiseur XML, et pour plus de détails sur la façon dont le supporté Les fonctionnalités XSD sont prises en charge.

4
John Saunders

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 System.InvalidOperationException Inutile lors de l'exécution lorsque vous sérialiserez.

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;
4
MarkJ

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

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é en utilisant contexte LoadFrom qui a de nombreux inconvénients à utiliser le contexte Load. Un peu de plaisir à retrouver celui-ci.

4
user7116

Les propriétés marquées avec l'attribut Obsolete ne sont pas sérialisées. Je n'ai pas testé avec l'attribut Deprecated mais je suppose qu'il agirait de la même manière.

3
James Hulse

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 le sont dans la sérialisation binaire.

3
Charles Graham

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 de distinction entre Message, ExampleMessageA et ExampleMessageB. Il ne sera sérialisé que vers et depuis la classe Message de base.

2
ilitirit

Faites attention aux types de sérialisation sans sérialisation explicite, cela peut entraîner des retards pendant que .Net les construit. J'ai découvert cela récemment lors de la sérialisation de RSAParameters .

2
Keith

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:

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

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

2
annakata

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

Je crois 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, donc les membres publics font tous référence à des valeurs nulles.

0
Dr8k