Kontynuując moje poprzednie pytanie , pracowałem nad uzyskaniem serializacji mojego modelu obiektowego do XML. Ale teraz mam problem (quelle niespodzianka!).
Problem polega na tym, że mam kolekcję, która jest abstrakcyjnym typem klasy bazowej, która jest zapełniana przez konkretne typy pochodne.
Pomyślałem, że byłoby dobrze po prostu dodać atrybuty XML do wszystkich zaangażowanych klas i wszystko byłoby brzoskwiniowe. Niestety tak nie jest!
Poszperałem więc trochę w Google i teraz rozumiem, dlaczego to nie działa. W tym jest w rzeczywistości robi jakąś sprytną odbicie w celu serializacji obiektów do / z XML, a od czasu jej podstawie abstrakcyjnego typu, nie może dowiedzieć się, co u diabła on rozmawia . W porządku.XmlSerializer
Natknąłem się na tę stronę w CodeProject, która wygląda na to, że może bardzo pomóc (jeszcze do przeczytania / skonsumowania), ale pomyślałem, że chciałbym również przenieść ten problem do tabeli StackOverflow, aby sprawdzić, czy masz jakieś schludne hacki / triki, aby to uruchomić w możliwie najszybszy / najlżejszy sposób.
Dodam jeszcze, że NIE CHCĘ zjeżdżać tą XmlInclude
trasą. Jest z nim po prostu za dużo sprzężenia, a ten obszar systemu jest intensywnie rozwijany, więc byłby to prawdziwy ból głowy związany z konserwacją!
źródło
Odpowiedzi:
Problem rozwiązany!
OK, więc w końcu tam dotarłem (trzeba przyznać, że z dużą pomocą stąd !).
Więc podsumuj:
Cele:
Zidentyfikowane problemy / punkty, na które należy zwrócić uwagę:
Rozwiązanie
Stworzyłem klasę ogólną, w której określasz typ ogólny jako typ abstrakcyjny, z którym będziesz pracować. Daje to klasie możliwość „tłumaczenia” między typem abstrakcyjnym a typem konkretnym, ponieważ możemy na stałe zakodować rzutowanie (tj. Możemy uzyskać więcej informacji niż XmlSerializer).
Następnie zaimplementowałem interfejs IXmlSerializable , jest to dość proste, ale podczas serializacji musimy upewnić się, że zapisujemy typ konkretnej klasy do XML, abyśmy mogli odrzucić go z powrotem podczas deserializacji. Należy również zauważyć, że musi być w pełni kwalifikowany, ponieważ zespoły, w których znajdują się dwie klasy, mogą się różnić. Jest oczywiście trochę sprawdzania typu i rzeczy, które muszą się tutaj wydarzyć.
Ponieważ XmlSerializer nie może rzutować, musimy dostarczyć kod, aby to zrobić, więc niejawny operator jest następnie przeciążany (nawet nie wiedziałem, że możesz to zrobić!).
Oto kod AbstractXmlSerializer:
using System; using System.Collections.Generic; using System.Text; using System.Xml.Serialization; namespace Utility.Xml { public class AbstractXmlSerializer<AbstractType> : IXmlSerializable { // Override the Implicit Conversions Since the XmlSerializer // Casts to/from the required types implicitly. public static implicit operator AbstractType(AbstractXmlSerializer<AbstractType> o) { return o.Data; } public static implicit operator AbstractXmlSerializer<AbstractType>(AbstractType o) { return o == null ? null : new AbstractXmlSerializer<AbstractType>(o); } private AbstractType _data; /// <summary> /// [Concrete] Data to be stored/is stored as XML. /// </summary> public AbstractType Data { get { return _data; } set { _data = value; } } /// <summary> /// **DO NOT USE** This is only added to enable XML Serialization. /// </summary> /// <remarks>DO NOT USE THIS CONSTRUCTOR</remarks> public AbstractXmlSerializer() { // Default Ctor (Required for Xml Serialization - DO NOT USE) } /// <summary> /// Initialises the Serializer to work with the given data. /// </summary> /// <param name="data">Concrete Object of the AbstractType Specified.</param> public AbstractXmlSerializer(AbstractType data) { _data = data; } #region IXmlSerializable Members public System.Xml.Schema.XmlSchema GetSchema() { return null; // this is fine as schema is unknown. } public void ReadXml(System.Xml.XmlReader reader) { // Cast the Data back from the Abstract Type. string typeAttrib = reader.GetAttribute("type"); // Ensure the Type was Specified if (typeAttrib == null) throw new ArgumentNullException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name + "' because no 'type' attribute was specified in the XML."); Type type = Type.GetType(typeAttrib); // Check the Type is Found. if (type == null) throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name + "' because the type specified in the XML was not found."); // Check the Type is a Subclass of the AbstractType. if (!type.IsSubclassOf(typeof(AbstractType))) throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name + "' because the Type specified in the XML differs ('" + type.Name + "')."); // Read the Data, Deserializing based on the (now known) concrete type. reader.ReadStartElement(); this.Data = (AbstractType)new XmlSerializer(type).Deserialize(reader); reader.ReadEndElement(); } public void WriteXml(System.Xml.XmlWriter writer) { // Write the Type Name to the XML Element as an Attrib and Serialize Type type = _data.GetType(); // BugFix: Assembly must be FQN since Types can/are external to current. writer.WriteAttributeString("type", type.AssemblyQualifiedName); new XmlSerializer(type).Serialize(writer, _data); } #endregion } }
Jak więc od tego momentu mamy powiedzieć XmlSerializer, aby współpracował z naszym serializatorem, a nie z domyślnym? Musimy przekazać nasz typ w ramach właściwości typu atrybutów XML, na przykład:
[XmlRoot("ClassWithAbstractCollection")] public class ClassWithAbstractCollection { private List<AbstractType> _list; [XmlArray("ListItems")] [XmlArrayItem("ListItem", Type = typeof(AbstractXmlSerializer<AbstractType>))] public List<AbstractType> List { get { return _list; } set { _list = value; } } private AbstractType _prop; [XmlElement("MyProperty", Type=typeof(AbstractXmlSerializer<AbstractType>))] public AbstractType MyProperty { get { return _prop; } set { _prop = value; } } public ClassWithAbstractCollection() { _list = new List<AbstractType>(); } }
Tutaj możesz zobaczyć, mamy kolekcję i pojedynczą właściwość, które są ujawniane, a wszystko, co musimy zrobić, to dodać parametr o nazwie typu do deklaracji Xml, łatwe! :RE
UWAGA: Jeśli użyjesz tego kodu, byłbym wdzięczny za wiadomość. Pomoże również przyciągnąć więcej osób do społeczności :)
Teraz, ale nie jestem pewien, co zrobić z odpowiedziami tutaj, ponieważ wszyscy mieli swoje plusy i minusy. Zmodyfikuję te, które uważam za przydatne (bez obrazy dla tych, które nie były) i zakończę to, gdy będę miał przedstawiciela :)
Ciekawy problem i dobra zabawa do rozwiązania! :)
źródło
private
lubprotected
wymusić, aby nie był dostępny dla innych klas.Jedną z rzeczy, na które należy zwrócić uwagę, jest fakt, że w konstruktorze XmlSerialiser można przekazać tablicę typów, które serializator może mieć trudności z rozpoznaniem. Musiałem tego używać kilka razy, gdy zbiór lub złożony zestaw struktur danych wymagał serializacji, a te typy żyły w różnych zespołach itp.
XmlSerialiser Constructor z parametrem extraTypes
EDYCJA: Dodałbym, że to podejście ma tę przewagę nad atrybutami XmlInclude itp., Że można wypracować sposób wykrywania i kompilowania listy możliwych konkretnych typów w czasie wykonywania i umieszczać je.
źródło
Poważnie, rozszerzalna struktura POCO nigdy nie zostanie niezawodnie serializowana do XML. Mówię to, ponieważ mogę zagwarantować, że ktoś przyjdzie, rozszerzy twoją klasę i zepsuje.
Należy przyjrzeć się użyciu języka XAML do serializacji wykresów obiektów. Jest przeznaczony do tego, podczas gdy serializacja XML nie.
Serializator i deserializator Xaml bez problemu obsługuje typy generyczne, a także kolekcje klas bazowych i interfejsów (o ile same kolekcje implementują
IList
lubIDictionary
). Istnieją pewne zastrzeżenia, takie jak oznaczanie właściwości kolekcji tylko do odczytu za pomocąDesignerSerializationAttribute
, ale przerobienie kodu w celu obsługi tych narożnych przypadków nie jest takie trudne.źródło
Tylko szybka aktualizacja, nie zapomniałem!
Po prostu robię więcej badań, wygląda na to, że jestem na drodze do zwycięzcy, po prostu muszę posortować kod.
Jak dotąd mam:
Wydaje się, że to zachowanie można przesłonić (oczekujący kod) przez utworzenie klasy proxy, która będzie działać jako pośrednik dla serializatora. Zasadniczo określi typ klasy pochodnej, a następnie serializuje ją w normalny sposób. Ta klasa proxy przekaże następnie ten kod XML do kopii zapasowej wiersza do głównego serializatora.
Patrz na przestrzeń! ^ _ ^
źródło
Z pewnością jest to rozwiązanie twojego problemu, ale jest inny problem, który nieco podważa twoją intencję używania „przenośnego” formatu XML. Złe dzieje się, gdy zdecydujesz się zmienić klasy w następnej wersji programu i musisz obsługiwać oba formaty serializacji - nowy i stary (ponieważ Twoi klienci nadal używają swoich starych plików / baz danych lub łączą się z Twój serwer przy użyciu starej wersji produktu). Ale nie możesz już używać tego serializatora, ponieważ używałeś
type.AssemblyQualifiedName
który wygląda jak
TopNamespace.SubNameSpace.ContainingClass+NestedClass, MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089
czyli zawiera atrybuty i wersję zestawu ...
Teraz, jeśli spróbujesz zmienić wersję zestawu lub zdecydujesz się go podpisać, ta deserializacja nie zadziała ...
źródło
Robiłem podobne rzeczy. To, co zwykle robię, to upewnienie się, że wszystkie atrybuty serializacji XML znajdują się w konkretnej klasie i po prostu mam wywołanie właściwości tej klasy do klas podstawowych (jeśli jest to wymagane) w celu pobrania informacji, które zostaną de / serializowane, gdy wywoła serializator te właściwości. To trochę więcej pracy z kodowaniem, ale działa znacznie lepiej niż próba zmuszenia serializatora do zrobienia właściwej rzeczy.
źródło
Jeszcze lepiej, używając notacji:
[XmlRoot] public class MyClass { public abstract class MyAbstract {} public class MyInherited : MyAbstract {} [XmlArray(), XmlArrayItem(typeof(MyInherited))] public MyAbstract[] Items {get; set; } }
źródło