Trucchi di serializzazione XML .NET? [chiuso]


121

Ho riscontrato alcuni trucchi durante la serializzazione XML C # che pensavo di condividere:


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

Qualche altro trucco di serializzazione XML là fuori?


Lookin per ulteriori trucchi lol, si potrebbe essere in grado di darmi una mano: stackoverflow.com/questions/2663836/...
Shimmy Weitzhandler

1
Inoltre, ti consigliamo di dare un'occhiata all'implementazione del dizionario serialzable di Charles Feduke, che ha fatto notare allo scrittore xml di notare che i membri attribuibili ai membri regolari devono essere serializzati dal serializzatore predefinito: deploymentzone.com/2008/09/19/…
Shimmy Weitzhandler

Questo non sembra catturare abbastanza tutti i trucchi. Sto impostando IEqualityComparer nel costruttore, ma non viene serializzato in questo codice. Qualche idea su come estendere questo dizionario per includere questo bit di informazioni? le informazioni potrebbero essere gestite tramite l'oggetto Type?
ColinCren

Risposte:


27

Un altro enorme trucco: quando si invia XML tramite una pagina Web (ASP.NET), non si desidera includere Unicode Byte-Order Mark . Naturalmente, i modi per utilizzare o meno la distinta materiali sono quasi gli stessi:

BAD (include BOM):

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

BENE:

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

È possibile passare esplicitamente false per indicare che non si desidera la distinta componenti. Notare la chiara, ovvia differenza tra Encoding.UTF8e UTF8Encoding.

I tre byte BOM aggiuntivi all'inizio sono (0xEFBBBF) o (239 187 191).

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


4
Il tuo commento sarebbe ancora più utile se non ci dicessi solo cosa, ma perché.
Neil

1
Questo non è realmente correlato alla serializzazione XML ... è solo un problema di XmlTextWriter
Thomas Levesque

7
-1: non correlato alla domanda e non dovresti utilizzare XmlTextWriterin .NET 2.0 o versioni successive.
John Saunders,

Link di riferimento molto utile. Grazie.
Anil Vangari

21

Non posso ancora fare commenti, quindi commenterò il post di Dr8k e farò un'altra osservazione. Variabili private che sono esposte come proprietà getter / setter pubbliche e vengono serializzate / deserializzate come tali tramite tali proprietà. L'abbiamo sempre fatto nel mio vecchio lavoro.

Una cosa da notare però è che se hai una logica in quelle proprietà, la logica viene eseguita, quindi a volte, l'ordine di serializzazione è davvero importante. I membri sono ordinati in modo implicito in base a come sono ordinati nel codice, ma non ci sono garanzie, soprattutto quando si eredita un altro oggetto. Ordinarli esplicitamente è un dolore nella parte posteriore.

Sono stato bruciato da questo in passato.


17
Ho trovato questo post mentre cercavo modi per impostare esplicitamente l'ordine dei campi. Questo viene fatto con gli attributi: [XmlElementAttribute (Order = 1)] public int Field {...} Lato negativo: l'attributo deve essere specificato per TUTTI i campi della classe e tutti i suoi discendenti! IMO Dovresti aggiungerlo al tuo post.
Cristian Diaconescu

15

Durante la serializzazione in una stringa XML da un flusso di memoria, assicurati di usare MemoryStream # ToArray () invece di MemoryStream # GetBuffer () o ti ritroverai con caratteri spazzatura che non verranno deserializzati correttamente (a causa del buffer aggiuntivo allocato).

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


3
direttamente dalla documentazione "Nota che il buffer contiene byte allocati che potrebbero essere inutilizzati. Ad esempio, se la stringa" test "viene scritta nell'oggetto MemoryStream, la lunghezza del buffer restituito da GetBuffer è 256, non 4, con 252 byte non utilizzato. Per ottenere solo i dati nel buffer, utilizzare il metodo ToArray; tuttavia, ToArray crea una copia dei dati in memoria. " msdn.microsoft.com/en-us/library/...
realgt

solo ora l'ho visto. Non suona più come una sciocchezza.
John Saunders

Mai sentito prima, il che è utile per il debug.
Ricky

10

Se il serializzatore rileva un membro / proprietà che ha un'interfaccia come tipo, non verrà serializzato. Ad esempio, quanto segue non verrà serializzato in XML:

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

Sebbene questo serializzerà:

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

Se ricevi un'eccezione con un messaggio "Tipo non risolto per il membro ...", questo potrebbe essere ciò che sta accadendo.
Kyle Krull

9

IEnumerables<T>che vengono generati tramite i rendimenti di rendimento non sono serializzabili. Questo perché il compilatore genera una classe separata per implementare il rendimento del rendimento e quella classe non è contrassegnata come serializzabile.


Questo si applica alla serializzazione "altra", ovvero l'attributo [Serializable]. Tuttavia, questo non funziona nemmeno per XmlSerializer.
Tim Robinson,


8

Non è possibile serializzare le proprietà di sola lettura. È necessario disporre di un getter e di un setter, anche se non si intende mai utilizzare la deserializzazione per trasformare XML in un oggetto.

Per lo stesso motivo, non è possibile serializzare proprietà che restituiscono interfacce: il deserializzatore non saprebbe quale classe concreta istanziare.


1
In realtà è possibile serializzare una proprietà di raccolta anche se non ha setter, ma deve essere inizializzata nel costruttore in modo che la deserializzazione possa aggiungervi elementi
Thomas Levesque,

7

Oh, eccone uno buono: poiché il codice di serializzazione XML viene generato e inserito in una DLL separata, non si ottiene alcun errore significativo quando c'è un errore nel codice che interrompe il serializzatore. Qualcosa come "impossibile individuare s3d3fsdf.dll". Bello.


11
È possibile generare quella DLL in anticipo utilizzando XML "Serializer Generator Tool (Sgen.exe)" e distribuirla con l'applicazione.
huseyint

6

Non è possibile serializzare un oggetto che non ha un costruttore senza parametri (è stato appena morso da quello).

E per qualche motivo, dalle seguenti proprietà, Value viene serializzato, ma non FullName:

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

Non sono mai riuscito a capire perché, ho solo cambiato Value in internal ...


4
Il costruttore senza parametri può essere privato / protetto. Sarà sufficiente per il serializzatore XML. Il problema con FullName è davvero strano, non dovrebbe accadere ...
Max Galkin

@ Yacoder: Forse perché no double?ma solo double?
abatishchev

FullName era probabilmente nulle quindi non genererà alcun XML durante la serializzazione
Jesper


4

Se l'assembly generato dalla serializzazione XML non è nello stesso contesto di caricamento del codice che tenta di usarlo, ti imbatterai in errori fantastici come:

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 di ciò per me era un plug-in caricato utilizzando il contesto LoadFrom che presenta molti svantaggi nell'utilizzo del contesto di caricamento. Piuttosto divertente rintracciarlo.




4

Se si tenta di serializzare un array List<T>o IEnumerable<T>che contiene istanze di sottoclassi di T, è necessario utilizzare XmlArrayItemAttribute per elencare tutti i sottotipi utilizzati. Altrimenti otterrai un inutile System.InvalidOperationExceptionin fase di esecuzione quando serializzi.

Ecco parte di un esempio completo dalla documentazione

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;

3

Le variabili / proprietà private non sono serializzate nel meccanismo predefinito per la serializzazione XML, ma sono nella serializzazione binaria.


2
Sì, se stai utilizzando la serializzazione XML "predefinita". Puoi specificare la logica di serializzazione XML personalizzata implementando IXmlSerializable nella tua classe e serializzare tutti i campi privati ​​di cui hai bisogno / desideri.
Max Galkin

1
Ebbene, questo è vero. Lo modificherò. Ma implementare quell'interfaccia è una sorta di rompicoglioni da quello che ricordo.
Charles Graham

3

Le proprietà contrassegnate con l' Obsoleteattributo non vengono serializzate. Non ho provato con l' Deprecatedattributo ma presumo che si comporterebbe allo stesso modo.


2

Non posso davvero spiegare questo, ma ho scoperto che non serializzerà:

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

ma questo:

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

E vale anche la pena notare che se stai serializzando un memstream, potresti voler cercare 0 prima di usarlo.


Penso che sia perché non può ricostruirlo. Nel secondo esempio può chiamare item.Add () per aggiungere elementi all'elenco. Non può farlo nel primo.
ilitirit

18
Usa: [XmlArray ("item"), XmlArrayItem ("myClass", typeof (myClass))]
RvdK

1
applausi per questo! impara qualcosa ogni giorno
annakata


2

Se il tuo XSD fa uso di gruppi di sostituzione, è probabile che tu non possa (de) serializzarlo automaticamente. Dovrai scrivere i tuoi serializzatori per gestire questo scenario.

Per esempio.

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

In questo esempio, una busta può contenere messaggi. Tuttavia, il serializzatore predefinito di .NET non distingue tra Message, ExampleMessageA e ExampleMessageB. Serializzerà solo da e verso la classe Message di base.


0

Le variabili / proprietà private non sono serializzate nella serializzazione XML, ma sono nella serializzazione binaria.

Credo che questo ti dia anche se stai esponendo i membri privati ​​attraverso proprietà pubbliche: i membri privati ​​non vengono serializzati, quindi i membri pubblici fanno tutti riferimento a valori nulli.


Questo non è vero. L'incastonatore della proprietà pubblica sarebbe stato chiamato e, presumibilmente, avrebbe costituito il membro privato.
John Saunders,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.