Problemy z serializacją XML .NET? [Zamknięte]

121

Podczas serializacji C # XML napotkałem kilka problemów, o których myślałem, że się nimi podzielę:


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

Czy są jakieś inne problemy z serializacją XML?

dziwne
źródło
Poszukaj więcej gotchas lol, być może będziesz w stanie mi pomóc: stackoverflow.com/questions/2663836/…
Shimmy Weitzhandler
1
Ponadto, będziesz chciał rzucić okiem na implementację słownika z możliwością serializacji Charlesa Feduke'a, kazał pisarzowi xml zauważyć, że między członkami przypisywanymi do zwykłych członków zostanie serializowany przez domyślny serializator: deploymentzone.com/2008/09/19/…
Shimmy Weitzhandler
To nie wygląda na to, że łapie wszystkie problemy. Ustawiam IEqualityComparer w konstruktorze, ale to nie jest serializowane w tym kodzie. Jakieś pomysły, jak rozszerzyć ten słownik o tę informację? czy te informacje mogą być obsługiwane za pośrednictwem obiektu Type?
ColinCren,

Odpowiedzi:

27

Kolejna wielka wpadka: podczas wyprowadzania XML przez stronę internetową (ASP.NET) nie chcesz dołączać znaku kolejności bajtów Unicode . Oczywiście sposoby korzystania z zestawienia komponentów są prawie takie same:

BAD (zawiera BOM):

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

DOBRY:

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

Możesz jawnie przekazać false, aby wskazać, że nie chcesz BOM. Zwróć uwagę na wyraźną, oczywistą różnicę między Encoding.UTF8i UTF8Encoding.

Trzy dodatkowe bajty BOM na początku to (0xEFBBBF) lub (239 187 191).

Źródła: http://chrislaco.com/blog/troubleshooting-common-problems-with-the-xmlserializer/

Kalid
źródło
4
Twój komentarz byłby jeszcze bardziej przydatny, gdybyś powiedział nam nie tylko co, ale dlaczego.
Neil
1
To nie jest tak naprawdę związane z serializacją XML ... to tylko problem XmlTextWriter
Thomas Levesque
7
-1: Nie ma związku z pytaniem i nie powinieneś używać XmlTextWriterw .NET 2.0 lub nowszym.
John Saunders
Bardzo pomocny link referencyjny. Dzięki.
Anil Vangari
21

Nie mogę jeszcze komentować, więc skomentuję post Dr8k i poczynię kolejną obserwację. Zmienne prywatne, które są ujawniane jako publiczne właściwości pobierające / ustawiające i są serializowane / deserializowane jako takie za pośrednictwem tych właściwości. Robiliśmy to cały czas w mojej starej pracy.

Należy jednak zauważyć, że jeśli masz jakąkolwiek logikę w tych właściwościach, logika jest uruchamiana, więc czasami kolejność serializacji ma znaczenie. Elementy członkowskie są niejawnie uporządkowane według kolejności w kodzie, ale nie ma żadnych gwarancji, zwłaszcza w przypadku dziedziczenia innego obiektu. Wyraźne zamawianie ich jest utrapieniem.

Zostałem przez to spalony w przeszłości.

Charles Graham
źródło
17
Znalazłem ten post, szukając sposobów na jawne ustawienie kolejności pól. Odbywa się to za pomocą atrybutów: [XmlElementAttribute (Order = 1)] public int Field {...} Wada: atrybut musi być określony dla WSZYSTKICH pól w klasie i wszystkich jej potomków! IMO Powinieneś to dodać do swojego postu.
Cristian Diaconescu
15

Podczas serializacji do ciągu XML ze strumienia pamięci, pamiętaj, aby użyć MemoryStream # ToArray () zamiast MemoryStream # GetBuffer (), w przeciwnym razie otrzymasz niepotrzebne znaki, które nie będą prawidłowo deserializować (z powodu przydzielonego dodatkowego buforu).

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

realgt
źródło
3
prosto z dokumentacji "Zauważ, że bufor zawiera przydzielone bajty, które mogą być nieużywane. Na przykład, jeśli ciąg" test "jest zapisany w obiekcie MemoryStream, długość bufora zwróconego przez GetBuffer wynosi 256, a nie 4, z 252 bajtami nieużywane. Aby uzyskać tylko dane w buforze, użyj metody ToArray; jednak ToArray tworzy kopię danych w pamięci. " msdn.microsoft.com/en-us/library/…
realgt
dopiero teraz to widziałem. To już nie brzmi jak nonsens.
John Saunders,
Nigdy wcześniej tego nie słyszałem, co jest pomocne przy debugowaniu.
Ricky
10

Jeśli serializator napotka element członkowski / właściwość, której typem jest interfejs, nie zostanie serializowany. Na przykład następujące elementy nie zostaną serializowane do XML:

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

Chociaż zostanie to serializowane:

public class ValuePair
{
    public object Value1 { get; set; }
    public object Value2 { get; set; }
}
Allon Guralnek
źródło
Jeśli pojawi się wyjątek z komunikatem „Typ nierozstrzygnięty dla członka…”, może to być przyczyną problemu.
Kyle Krull
9

IEnumerables<T>które są generowane przez zwroty zysków nie podlegają serializacji. Dzieje się tak, ponieważ kompilator generuje oddzielną klasę w celu zaimplementowania zwrotu zysku i ta klasa nie jest oznaczona jako możliwa do serializacji.

abatishchev
źródło
Dotyczy to serializacji „inne”, tj. Atrybutu [Serializable]. Jednak nie działa to również w przypadku XmlSerializer.
Tim Robinson
8

Nie można serializować właściwości tylko do odczytu. Musisz mieć metodę pobierającą i ustawiającą, nawet jeśli nigdy nie zamierzasz używać deserializacji do przekształcania XML w obiekt.

Z tego samego powodu nie można serializować właściwości, które zwracają interfejsy: deserializator nie wiedziałby, jaką konkretną klasę należy utworzyć.

Tim Robinson
źródło
1
W rzeczywistości można serializować właściwość kolekcji, nawet jeśli nie ma ona metody ustawiającej, ale musi zostać zainicjowana w konstruktorze, aby deserializacja mogła dodać do niej elementy
Thomas Levesque
7

Och, oto dobry: ponieważ kod serializacji XML jest generowany i umieszczany w oddzielnej bibliotece DLL, nie pojawia się żaden znaczący błąd, gdy w kodzie jest błąd, który powoduje uszkodzenie serializatora. Coś w rodzaju „Nie można zlokalizować s3d3fsdf.dll”. Miły.

Eric Z Beard
źródło
11
Możesz wygenerować tę bibliotekę DLL z wyprzedzeniem za pomocą narzędzia XML „Serializer Generator Tool (Sgen.exe)” i wdrożyć ją w aplikacji.
huseyint,
6

Nie można serializować obiektu, który nie ma konstruktora bez parametrów (właśnie został przez niego ugryziony).

Z jakiegoś powodu z następujących właściwości zostanie serializowana wartość, ale nie FullName:

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

Nigdy nie udało mi się ustalić, dlaczego, po prostu zmieniłem wartość na wewnętrzną ...

Benjol
źródło
4
Konstruktor bez parametrów może być prywatny / chroniony. Wystarczy dla serializatora XML. Problem z FullName jest naprawdę dziwny, nie powinien się zdarzyć ...
Max Galkin,
@Yacoder: Może dlatego, że nie, double?ale tylko double?
abatishchev
FullName był prawdopodobnie nulli dlatego nie będzie generował żadnego XML po serializacji
Jesper
4

Jeśli zestaw wygenerowany przez serializację XML nie znajduje się w tym samym kontekście ładowania, co kod próbujący go użyć, napotkasz niesamowite błędy, takie jak:

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)

Przyczyną tego była dla mnie wtyczka ładowana przy użyciu kontekstu LoadFrom, która ma wiele wad w porównaniu z kontekstem Load. Całkiem niezła zabawa w tropieniu tego.

user7116
źródło
4

Jeśli próbujesz serializować tablicę, List<T>lub IEnumerable<T>zawierający instancji podklasy o T, trzeba użyć XmlArrayItemAttribute do listy wszystkich używanej podtypy. W przeciwnym razie System.InvalidOperationExceptionpodczas serializacji pojawi się niepomocny plik .

Oto część pełnego przykładu z dokumentacji

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
źródło
3

Prywatne zmienne / właściwości nie są serializowane w domyślnym mechanizmie serializacji XML, ale są w serializacji binarnej.

Charles Graham
źródło
2
Tak, jeśli używasz „domyślnej” serializacji XML. Możesz określić niestandardową logikę serializacji XML implementującą IXmlSerializable w Twojej klasie i serializować dowolne pola prywatne, których potrzebujesz / chcesz.
Max Galkin,
1
Cóż, to prawda. Zmienię to. Ale implementacja tego interfejsu jest trochę upierdliwa z tego, co pamiętam.
Charles Graham,
3

Właściwości oznaczone Obsoleteatrybutem nie są serializowane. Nie testowałem Deprecatedatrybutu, ale zakładam, że zadziałaby w ten sam sposób.

James Hulse
źródło
2

Naprawdę nie mogę tego wyjaśnić, ale stwierdziłem, że to nie będzie serializowane:

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

ale to będzie:

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

Warto również zauważyć, że jeśli serializujesz do strumienia memów, możesz chcieć poszukać 0, zanim go użyjesz.

annakata
źródło
Myślę, że to dlatego, że nie może go odbudować. W drugim przykładzie może wywołać item.Add (), aby dodać elementy do listy. Nie może tego zrobić w pierwszym.
ilitirit
18
Zastosowanie: [XmlArray ("item"), XmlArrayItem ("myClass", typeof (myClass))]
RvdK
1
okrzyki za to! ucz się czegoś każdego dnia
annakata,
2

Jeśli XSD korzysta z grup zastępczych, prawdopodobnie nie możesz (de) serializować go automatycznie. Będziesz musiał napisać własne serializatory, aby obsłużyć ten scenariusz.

Na przykład.

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

W tym przykładzie Koperta może zawierać Wiadomości. Jednak domyślny serializator platformy .NET nie rozróżnia Message, ExampleMessageA i ExampleMessageB. Będzie tylko serializować do iz podstawowej klasy Message.

ilitirit
źródło
0

Prywatne zmienne / właściwości nie są serializowane w serializacji XML, ale są w serializacji binarnej.

Uważam, że jest to również możliwe, jeśli ujawniasz członków prywatnych za pośrednictwem właściwości publicznych - członkowie prywatni nie są serializowani, więc wszyscy członkowie publiczni odwołują się do wartości null.

Dr8k
źródło
To nie jest prawda. Powołany zostałby ustanawiający własność publiczną, który przypuszczalnie ustanowiłby członka prywatnego.
John Saunders