¿Problemas de serialización XML .NET? [cerrado]

121

Me encontré con algunos problemas al hacer la serialización C # XML que pensé que compartiría:


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

¿Alguna otra trampa de serialización XML por ahí?

kurious
fuente
Si buscas más cosas, jaja, podrías ayudarme: stackoverflow.com/questions/2663836/…
Shimmy Weitzhandler
1
Además, querrá echar un vistazo a la implementación de Charles Feduke del diccionario serializable, hizo que el escritor xml se diera cuenta entre los miembros atribuibles a los miembros regulares para que el serializador predeterminado serialice: deplozionzone.com/2008/09/19/…
Shimmy Weitzhandler
Esto no parece que atrape todas las trampas. Estoy configurando IEqualityComparer en el constructor, pero eso no se serializa en este código. ¿Alguna idea sobre cómo extender este Diccionario para incluir este bit de información? ¿se podría manejar esa información a través del objeto Type?
ColinCren

Respuestas:

27

Otro gran problema: al generar XML a través de una página web (ASP.NET), no desea incluir la marca de orden de bytes Unicode . Por supuesto, las formas de usar o no usar la lista de materiales son casi las mismas:

MALO (incluye BOM):

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

BUENO:

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

Puede pasar explícitamente falso para indicar que no desea la lista de materiales. Observe la clara y obvia diferencia entre Encoding.UTF8y UTF8Encoding.

Los tres bytes BOM adicionales al principio son (0xEFBBBF) o (239 187 191).

Referencia: http://chrislaco.com/blog/troubleshooting-common-problems-with-the-xmlserializer/

Kalid
fuente
44
Su comentario sería aún más útil si nos dijera no solo qué, sino por qué.
Neil
1
Esto no está realmente relacionado con la serialización XML ... es solo un problema de XmlTextWriter
Thomas Levesque
77
-1: No está relacionado con la pregunta, y no deberías usarlo XmlTextWriteren .NET 2.0 o superior.
John Saunders
Enlace de referencia muy útil. Gracias.
Anil Vangari
21

Todavía no puedo hacer comentarios, así que comentaré la publicación de Dr8k y haré otra observación. Las variables privadas que se exponen como propiedades públicas de obtención / establecimiento, y se serializan / deserializan como tales a través de esas propiedades. Lo hicimos en mi antiguo trabajo todo el tiempo.

Sin embargo, una cosa a tener en cuenta es que si tiene alguna lógica en esas propiedades, la lógica se ejecuta, por lo que a veces, el orden de serialización realmente importa. Los miembros se ordenan implícitamente según cómo se ordenan en el código, pero no hay garantías, especialmente cuando se hereda otro objeto. Ordenarlos explícitamente es un dolor en la parte trasera.

Me quemé por esto en el pasado.

Charles Graham
fuente
17
Encontré esta publicación mientras buscaba formas de establecer explícitamente el orden de los campos. Esto se hace con los atributos: [XmlElementAttribute (Order = 1)] public int Field {...} Desventaja: ¡el atributo debe especificarse para TODOS los campos de la clase y todos sus descendientes! OMI Deberías agregar esto a tu publicación.
Cristian Diaconescu
15

Al serializar en una cadena XML desde una secuencia de memoria, asegúrese de usar MemoryStream # ToArray () en lugar de MemoryStream # GetBuffer () o terminará con caracteres basura que no se deserializarán correctamente (debido al búfer adicional asignado).

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

realgt
fuente
3
directamente desde los documentos "Tenga en cuenta que el búfer contiene bytes asignados que podrían no utilizarse. Por ejemplo, si la cadena" prueba "se escribe en el objeto MemoryStream, la longitud del búfer devuelto por GetBuffer es 256, no 4, con 252 bytes no utilizado. Para obtener solo los datos en el búfer, utilice el método ToArray; sin embargo, ToArray crea una copia de los datos en la memoria ". msdn.microsoft.com/en-us/library/…
realgt
Solo ahora vi esto. Ya no suena como una tontería.
John Saunders
Nunca escuché esto antes, lo cual es útil para la depuración.
Ricky
10

Si el serializador encuentra un miembro / propiedad que tiene una interfaz como su tipo, no se serializará. Por ejemplo, lo siguiente no se serializará en XML:

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

Aunque esto serializará:

public class ValuePair
{
    public object Value1 { get; set; }
    public object Value2 { get; set; }
}
Allon Guralnek
fuente
Si recibe una excepción con un mensaje "Tipo no resuelto para miembro ...", esto puede ser lo que está sucediendo.
Kyle Krull
9

IEnumerables<T>que se generan a través de rendimientos de rendimiento no son serializables. Esto se debe a que el compilador genera una clase separada para implementar el rendimiento y esa clase no está marcada como serializable.

abatishchev
fuente
Esto se aplica a la 'otra' serialización, es decir, el atributo [Serializable]. Sin embargo, esto tampoco funciona para XmlSerializer.
Tim Robinson
8

No puede serializar propiedades de solo lectura. Debe tener un getter y un setter, incluso si nunca tiene la intención de utilizar la deserialización para convertir XML en un objeto.

Por la misma razón, no puede serializar propiedades que devuelven interfaces: el deserializador no sabría qué clase concreta instanciar.

Tim Robinson
fuente
1
En realidad, puede serializar una propiedad de colección incluso si no tiene configurador, pero debe inicializarse en el constructor para que la deserialización pueda agregarle elementos
Thomas Levesque
7

Ah, aquí hay una buena: dado que el código de serialización XML se genera y se coloca en una DLL separada, no se produce ningún error significativo cuando hay un error en su código que rompe el serializador. Simplemente algo así como "no se puede localizar s3d3fsdf.dll". Agradable.

Eric Z Beard
fuente
11
Puede generar esa DLL con anticipación utilizando XML "Serializer Generator Tool (Sgen.exe)" e implementar con su aplicación.
huseyint
6

No se puede serializar un objeto que no tiene un constructor sin parámetros (ese solo lo mordió).

Y por alguna razón, de las siguientes propiedades, Value se serializa, pero no FullName:

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

Nunca pude averiguar por qué, simplemente cambié el valor a interno ...

Benjol
fuente
44
El constructor sin parámetros puede ser privado / protegido. Será suficiente para el serializador XML. El problema con NombreCompleto es muy extraño, no debería ocurrir ...
Max Galkin
@Yacoder: ¿Quizás porque no double?pero solo double?
abatishchev
FullName probablemente nully, por lo tanto, no generará ningún XML cuando se serialice
Jesper
4

Si su ensamblaje generado por la serialización XML no está en el mismo contexto de carga que el código que intenta usarlo, se encontrará con errores increíbles como:

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 causa de esto para mí fue un complemento cargado usando el contexto LoadFrom que tiene muchas desventajas al usar el contexto Load. Bastante divertido rastrearlo.

usuario7116
fuente
4

Si intenta serializar una matriz, List<T>o IEnumerable<T>que contiene instancias de subclases de T, debe usar XmlArrayItemAttribute para enumerar todos los subtipos que se utilizan. De lo contrario, obtendrá un poco útil System.InvalidOperationExceptionen tiempo de ejecución cuando serialice.

Aquí hay parte de un ejemplo completo de la documentación.

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
fuente
3

Las variables / propiedades privadas no se serializan en el mecanismo predeterminado para la serialización XML, pero sí en la serialización binaria.

Charles Graham
fuente
2
Sí, si está utilizando la serialización XML "predeterminada". Puede especificar una lógica de serialización XML personalizada que implemente IXmlSerializable en su clase y serializar cualquier campo privado que necesite / desee.
Max Galkin
1
Bueno, esto es verdad. Editaré esto. Pero implementar esa interfaz es una molestia por lo que recuerdo.
Charles Graham
3

Las propiedades marcadas con el Obsoleteatributo no se serializan. No he probado con el Deprecatedatributo, pero supongo que actuaría de la misma manera.

James Hulse
fuente
2

Realmente no puedo explicar esto, pero descubrí que esto no se serializará:

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

pero esto:

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

Y también vale la pena señalar que si está serializando a un memstream, es posible que desee buscar 0 antes de usarlo.

annakata
fuente
Creo que es porque no puede reconstruirlo. En el segundo ejemplo, puede llamar a item.Add () para agregar elementos a la Lista. No puede hacerlo en el primero.
ilitirit
18
Uso: [XmlArray ("item"), XmlArrayItem ("myClass", typeof (myClass))]
RvdK
1
aplausos por eso! aprender algo todos los días
annakata
2

Tenga cuidado con los tipos de serialización sin una serialización explícita, puede generar demoras mientras .Net los construye. Descubrí esto recientemente mientras serializaba RSAParameters .

Keith
fuente
2

Si su XSD utiliza grupos de sustitución, entonces es probable que no pueda (des) serializarlo automáticamente. Tendrá que escribir sus propios serializadores para manejar este escenario.

P.ej.

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

En este ejemplo, un sobre puede contener mensajes. Sin embargo, el serializador predeterminado de .NET no distingue entre Message, ExampleMessageA y ExampleMessageB. Solo se serializará hacia y desde la clase de mensaje base.

ilitirit
fuente
0

Las variables / propiedades privadas no se serializan en la serialización XML, pero sí en la serialización binaria.

Creo que esto también lo atrapa si está exponiendo a los miembros privados a través de propiedades públicas: los miembros privados no se serializan, por lo que los miembros públicos hacen referencia a valores nulos.

Dr8k
fuente
Esto no es verdad. El emisor de la propiedad pública se llamaría y, presumiblemente, establecería al miembro privado.
John Saunders