Ordinamento di serializzazione .NET

Sto provando a serializzare alcuni oggetti usando XmlSerializer e l’ereditarietà ma sto avendo qualche problema con l’ordine del risultato.

Di seguito è riportato un esempio simile a quello che ho impostato: ~

public class SerializableBase { [XmlElement(Order = 1)] public bool Property1 { get; set;} [XmlElement(Order = 3)] public bool Property3 { get; set;} } [XmlRoot("Object")] public class SerializableObject1 : SerializableBase { } [XmlRoot("Object")] public class SerializableObject2 : SerializableBase { [XmlElement(Order = 2)] public bool Property2 { get; set;} } 

Il risultato che voglio è il seguente: ~

      

Comunque sto ottenendo un risultato di: ~

      

Qualcuno sa se è ansible o di qualsiasi alternativa?

Grazie

Tecnicamente, da una prospettiva xml pura, direi che questa è probabilmente una brutta cosa da voler fare.

.NET nasconde gran parte della complessità di cose come XmlSerialization – in questo caso, nasconde lo schema a cui il tuo xml serializzato dovrebbe conformarsi.

Lo schema dedotto utilizzerà gli elementi della sequenza per descrivere il tipo di base e i tipi di estensione. Ciò richiede un ordine rigoroso, anche se il deserializzatore è meno rigido e accetta gli elementi fuori servizio.

Negli schemi xml, quando si definiscono i tipi di estensione, gli elementi aggiuntivi della class figlio devono venire dopo gli elementi della class base.

avresti essenzialmente uno schema che assomiglia a qualcosa (tag xml-y rimossi per chiarezza)

 base sequence prop1 prop3 derived1 extends base sequence  derived2 extends base sequence prop2 

Non c’è modo di incollare un segnaposto tra prop1 e prop3 per indicare dove possono andare le proprietà del xml derivato.

Alla fine, c’è una discrepanza tra il tuo formato dati e il tuo object business. Probabilmente la tua migliore alternativa è definire un object per gestire la serializzazione xml.

Per esempio

 [XmlRoot("Object") public class SerializableObjectForPersistance { [XmlElement(Order = 1)] public bool Property1 { get; set; } [XmlElement(Order = 2, IsNullable=true)] public bool Property2 { get; set; } [XmlElement(Order = 3)] public bool Property3 { get; set; } } 

Questo separa il codice di serializzazione xml dal modello a oggetti. Copia tutti i valori da SerializableObject1 o SerializableObject2 a SerializableObjectForPersistance, quindi serializza.

In sostanza, se si desidera un tale controllo specifico sul formato del proprio xml serializzato che non si nutre con il framework di serializzazione xml, è necessario disaccoppiare la progettazione dell’object business (la struttura di ereditarietà in questo caso) e la responsabilità della serializzazione di tale object d’affari.

EDIT: questo approccio non funziona . Ho lasciato il post in modo che le persone possano evitare questa linea di pensiero.

Il serializzatore agisce in modo ricorsivo. C’è un vantaggio in questo; sulla deserializzazione, il processo di deserializzazione può leggere la class base, quindi la class derivata. Ciò significa che una proprietà sulla class derivata non è impostata prima delle proprietà sulla base, il che potrebbe causare problemi.

Se è davvero importante (e non sono sicuro del motivo per cui è importante averli in ordine), allora puoi provare questo –

1) rendere la class di base ‘Property1 e Property3 virtual. 2) sovrascriverli con proprietà triviali nella class derivata. Per esempio

 public class SerializableBase { [XmlElement(Order = 1)] public virtual bool Property1 { get; set;} [XmlElement(Order = 3)] public virtual bool Property3 { get; set;} } [XmlRoot("Object")] public class SerializableObject1 : SerializableBase { } [XmlRoot("Object")] public class SerializableObject2 : SerializableBase { [XmlElement(Order = 1)] public override bool Property1 { get { return base.Property1; } set { base.Property1 = value; } } [XmlElement(Order = 2)] public bool Property2 { get; set;} [XmlElement(Order = 3)] public override bool Property3 { get { return base.Property3; } set { base.Property3 = value; } } } 

Ciò pone una concreta implementazione della proprietà sulla class più derivata e l’ordine dovrebbe essere rispettato.

Sembra che la class XmlSerializer serializzi il tipo di base e quindi i tipi derivati ​​in quell’ordine e stia rispettando solo la proprietà Order all’interno di ogni class individualmente. Anche se l’ordine non è totalmente quello che vuoi, dovrebbe comunque essere deserializzato correttamente. Se proprio devi avere l’ordine, devi scrivere un serializzatore xml personalizzato. Vorrei mettere in guardia contro questo, perché .NET XmlSerializer fa un sacco di gestione speciale per voi. Puoi descrivere perché hai bisogno di cose nell’ordine che menzioni?

Questo post è piuttosto vecchio ora, ma ho avuto un problema simile in WCF di recente, e ho trovato una soluzione simile a quella di Steve Cooper sopra, ma che funziona, e presumibilmente funzionerà anche per la serializzazione XML.

Se rimuovi gli attributi XmlElement dalla class base e aggiungi una copia di ogni proprietà con un nome diverso alle classi derivate che accedono al valore di base tramite get / set, le copie possono essere serializzate con il nome appropriato assegnato utilizzando un XmlElementAttribute e speriamo di poter serializzare nell’ordine predefinito:

 public class SerializableBase { public bool Property1 { get; set;} public bool Property3 { get; set;} } [XmlRoot("Object")] public class SerializableObject : SerializableBase { [XmlElement("Property1")] public bool copyOfProperty1 { get { return base.Property1; } set { base.Property1 = value; } } [XmlElement] public bool Property2 { get; set;} [XmlElement("Property3")] public bool copyOfProperty3 { get { return base.Property3; } set { base.Property3 = value; } } } 

Ho anche aggiunto un’interfaccia da aggiungere alle classi derivate, in modo che le copie possano essere rese obbligatorie:

 interface ISerializableObjectEnsureProperties { bool copyOfProperty1 { get; set; } bool copyOfProperty2 { get; set; } } 

Questo non è essenziale, ma significa che posso verificare che tutto sia implementato in fase di compilazione, piuttosto che controllare l’XML risultante. Inizialmente avevo creato queste proprietà astratte di SerializableBase, ma queste poi serializzarono prima (con la class base), che ora realizzo logico.

Questo è chiamato nel solito modo cambiando una riga sopra:

 public class SerializableObject : SerializableBase, ISerializableObjectEnsureProperties 

Ho solo provato questo in WCF, e ho portato il concetto alla serializzazione XML senza compilare, quindi se questo non funziona, scuse, ma mi aspetterei che si comporti allo stesso modo – Sono sicuro che qualcuno mi lascerà sapere se non …

So che questa domanda è scaduta; tuttavia, ecco una soluzione per questo problema:

Il nome del metodo dovrebbe sempre iniziare con ShouldSerialize e quindi terminare con il nome della proprietà. Quindi devi semplicemente restituire un valore booleano in base a qualsiasi condizionale che desideri, o se serializzare il valore o meno.

 public class SerializableBase { public bool Property1 { get; set;} public bool Property2 { get; set;} public bool Property3 { get; set;} public virtual bool ShouldSerializeProperty2 { get { return false; } } } [XmlRoot("Object")] public class SerializableObject1 : SerializableBase { } [XmlRoot("Object")] public class SerializableObject2 : SerializableBase { public override bool ShouldSerializeProperty2 { get { return true; } } } 

Il risultato quando si utilizza SerializableObject2: ~

      

Il risultato quando si utilizza SerializableObject1: ~

     

Spero che questo aiuti molti altri!

Come ha detto Nader, forse pensa di creare un design più accoppiato. Tuttavia, nel mio caso, l’accoppiamento libero non era appropriato. Ecco la mia gerarchia di classi e il modo in cui propongo di risolvere il problema senza utilizzare la serializzazione personalizzata o DTO.

Nel mio progetto, sto costruendo un sacco di oggetti per rappresentare pezzi di un documento XML che verranno inviati tramite un servizio web. C’è un numero molto grande di pezzi. Non tutti vengono inviati ad ogni richiesta (in realtà, in questo esempio, sto modellando una risposta, ma i concetti sono gli stessi). Questi pezzi vengono utilizzati come blocchi predefiniti per assemblare una richiesta (o smontare una risposta, in questo caso). Quindi ecco un esempio di utilizzo di aggregazione / incapsulamento per realizzare l’ordine desiderato nonostante la gerarchia di ereditarietà.

 [Serializable] public abstract class ElementBase { // This constructor sets up the default namespace for all of my objects. Every // Xml Element class will inherit from this class. internal ElementBase() { this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] { new XmlQualifiedName(string.Empty, "urn:my-default-namespace:XSD:1") }); } [XmlNamespacesDeclaration] public XmlSerializerNamespaces Namespaces { get { return this._namespaces; } } private XmlSerializationNamespaces _namespaces; } [Serializable] public abstract class ServiceBase : ElementBase { private ServiceBase() { } public ServiceBase(Guid requestId, Guid? asyncRequestId = null, Identifier name = null) { this._requestId = requestId; this._asyncRequestId = asyncRequestId; this._name = name; } public Guid RequestId { get { return this._requestId; } set { this._requestId = value; } } private Guid _requestId; public Guid? AsyncRequestId { get { return this._asyncRequestId; } set { this._asyncRequestId = value; } } private Guid? _asyncRequestId; public bool AsyncRequestIdSpecified { get { return this._asyncRequestId == null && this._asyncRequestId.HasValue; } set { /* XmlSerializer requires both a getter and a setter.*/ ; } } public Identifier Name { get { return this._name; } set { this._name; } } private Identifier _name; } [Serializable] public abstract class ServiceResponseBase : ServiceBase { private ServiceBase _serviceBase; private ServiceResponseBase() { } public ServiceResponseBase(Guid requestId, Guid? asyncRequestId = null, Identifier name = null, Status status = null) { this._serviceBase = new ServiceBase(requestId, asyncRequestId, name); this._status = status; } public Guid RequestId { get { return this._serviceBase.RequestId; } set { this._serviceBase.RequestId = value; } } public Guid? AsyncRequestId { get { return this._serviceBase.AsyncRequestId; } set { this._serviceBase.AsyncRequestId = value; } } public bool AsynceRequestIdSpecified { get { return this._serviceBase.AsyncRequestIdSpecified; } set { ; } } public Identifier Name { get { return this._serviceBase.Name; } set { this._serviceBase.Name = value; } } public Status Status { get { return this._status; } set { this._status = value; } } } [Serializable] [XmlRoot(Namespace = "urn:my-default-namespace:XSD:1")] public class BankServiceResponse : ServiceResponseBase { // Determines if the class is being deserialized. private bool _isDeserializing; private ServiceResponseBase _serviceResponseBase; // Constructor used by XmlSerializer. // This is special because I require a non-null List of items later on. private BankServiceResponse() { this._isDeserializing = true; this._serviceResponseBase = new ServiceResponseBase(); } // Constructor used for unit testing internal BankServiceResponse(bool isDeserializing = false) { this._isDeserializing = isDeserializing; this._serviceResponseBase = new ServiceResponseBase(); } public BankServiceResponse(Guid requestId, List responses, Guid? asyncRequestId = null, Identifier name = null, Status status = null) { if (responses == null || responses.Count == 0) throw new ArgumentNullException("The list cannot be null or empty", "responses"); this._serviceResponseBase = new ServiceResponseBase(requestId, asyncRequestId, name, status); this._responses = responses; } [XmlElement(Order = 1)] public Status Status { get { return this._serviceResponseBase.Status; } set { this._serviceResponseBase.Status = value; } } [XmlElement(Order = 2)] public Guid RequestId { get { return this._serviceResponseBase.RequestId; } set { this._serviceResponseBase.RequestId = value; } } [XmlElement(Order = 3)] public Guid? AsyncRequestId { get { return this._serviceResponseBase.AsyncRequestId; } set { this._serviceResponseBase.AsyncRequestId = value; } } [XmlIgnore] public bool AsyncRequestIdSpecified { get { return this._serviceResponseBase.AsyncRequestIdSpecified; } set { ; } // Must have this for XmlSerializer. } [XmlElement(Order = 4)] public Identifer Name { get { return this._serviceResponseBase.Name; } set { this._serviceResponseBase.Name; } } [XmlElement(Order = 5)] public List Responses { get { return this._responses; } set { if (this._isDeserializing && this._responses != null && this._responses.Count > 0) this._isDeserializing = false; if (!this._isDeserializing && (value == null || value.Count == 0)) throw new ArgumentNullException("List cannot be null or empty.", "value"); this._responses = value; } } private List _responses; } 

Quindi, mentre devo creare le proprietà per tutte le classi contenute, posso debind qualsiasi logica personalizzata che potrei avere nei setter / getter della proprietà della class contenuta semplicemente usando le proprietà della class contenuta quando si accede alle proprietà della class foglia. Poiché non c’è eredità, posso decorare tutte le proprietà della class foglia con l’attributo XmlElementAttribute e utilizzare qualsiasi ordine che ritenga opportuno.


AGGIORNARE:

Sono tornato a rivisitare questo articolo perché le mie decisioni di progettazione sull’utilizzo dell’ereditarietà delle classi sono tornate a mordermi di nuovo. Mentre la mia soluzione sopra funziona, la sto usando, penso davvero che la soluzione di Nader sia la migliore e dovrebbe essere considerata prima della soluzione che ho presentato. In effetti, sto facendo +1 su di lui oggi! Mi piace molto la sua risposta, e se mai avrò l’opportunità di ridefinire il mio attuale progetto, separerò definitivamente l’object business dalla logica di serializzazione per oggetti che altrimenti trarrebbero enormi benefici dall’ereditarietà per semplificare il codice e renderlo più semplice per gli altri da usare e capire.

Grazie per aver postato la tua risposta, Nader, poiché penso che molti la troveranno molto istruttiva e utile.