2009-06-19 20 views
13

sto cercando di serializzare alcuni oggetti utilizzando XmlSerializer e l'ereditarietà, ma sto avendo alcuni problemi con l'ordinazione il risultato.NET serializzazione ordinazione

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

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 è la seguente: ~

<Object> 
    <Property1></Property1> 
    <Property2></Property2> 
    <Property3></Property3> 
</Object> 

Comunque io sono sempre il risultato di: ~

<Object> 
    <Property1></Property1> 
    <Property3></Property3> 
    <Property2></Property2> 
</Object> 

qualcuno sa se è possibile o di qualsiasi alternativa?

Grazie

+0

Ho avuto un problema simile a questo in cui avevo bisogno che la proprietà nella classe derivata appaia per ultima nel messaggio SOAP, la mia soluzione era quella di aggiungere la proprietà come interna nella classe base, quindi nasconderlo con la "nuova" parola chiave nella classe derivata. Vedi la mia risposta [qui] (http://stackoverflow.com/questions/22174311/wcf-serialization-order-issue/22177272#22177272). Spero che sia d'aiuto. –

risposta

3

Sembra che la classe XmlSerializer serializza il tipo di base e poi derivato tipi nell'ordine e rispetta solo la proprietà dell'Ordine all'interno di ogni classe singolarmente. 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?

3

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 classe base, quindi la classe derivata. Ciò significa che una proprietà sulla classe derivata non è impostata prima delle proprietà sulla base, il che potrebbe causare problemi.

Se davvero è importante (e non sono sicuro perché è importante per ottenere questi in ordine), allora si può provare questo -

1) rendere la classe base Property1 e Property3 virtuale. 2) sovrascrivile con proprietà triviali nella classe derivata. Es.

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

} 

Questo pone una implementazione concreta della proprietà sulla classe più derivata e l'ordine deve essere rispettato.

+0

Ho provato questo - non ha funzionato davvero –

+0

Ah, scusa - avrei pensato che avesse funzionato guardando la classe che conteneva un'implementazione concreta, ma chiaramente no. Aggiornerò il post per indicare che questo approccio non funziona. –

+0

era ancora una buona idea :) –

15

Tecnicamente, dal punto di vista XML puro, direi che questo è probabilmente una cattiva cosa di voler fare.

.NET nasconde gran parte della complessità di cose come XmlSerialization - in questo caso, si nasconde lo schema a cui il XML serializzato deve conformarsi.

Disposizione dedotto utilizzerà elementi di sequenza per descrivere il tipo di base, ei 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 classe figlio devono essere dopo gli elementi dalla classe base.

si avrebbe in sostanza avere uno schema che sembra qualcosa di simile (tag XML-y rimosse per chiarezza)

base 
    sequence 
    prop1 
    prop3 

derived1 extends base 
    sequence 
    <empty> 

derived2 extends base 
    sequence 
    prop2 

non c'è modo di attaccare un segnaposto tra prop1 e prop3 per indicare dove le proprietà dal derivato xml può andare.

Alla fine, si verifica una mancata corrispondenza tra il formato dei dati e l'oggetto aziendale. Probabilmente la tua migliore alternativa è definire un oggetto 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 è proprio jive con il framework di serializzazione xml delle aspettative, è necessario separare la progettazione dell'oggetto business (struttura ereditaria in questo caso) e la responsabilità per la serializzazione di tale oggetto business.

+0

+1, lo schema è un punto importante ma facilmente trascurato. – shambulator

+1

Si fa un buon esempio sul perché l'ordinamento non si comporti "come previsto", perché stiamo pensando in termini di OOP e non di schemi XML. Nel mio caso particolare, l'accoppiamento libero non è un disegno appropriato (di nuovo, questa è una nicchia - cerca sempre l'accoppiamento libero!), E se questa è la tua situazione, potresti sempre provare l'aggregazione, dove l'oggetto "bambino" contiene oggetto "genitore". Otterrai comunque incapsulamento e riusabilità, ma puoi anche specificare, per il bambino, l'esatto ordine degli elementi. – fourpastmidnight

+0

@Nader Ho fatto riferimento alla risposta nella mia risposta a questa domanda. Mentre la mia soluzione funziona, la tua è chiaramente superiore. Non ero corretto quando affermavo che l'accoppiamento libero non era appropriato nel mio progetto. Se mai avrò la possibilità di refactoring, sto usando i tuoi consigli! – fourpastmidnight

0

Come Nader ha detto, 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<T> 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<BankResponse> 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<BankResponse> 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<BankResponse> _responses; 
} 

Così, mentre io devo creare le proprietà per tutte le classi contenute, posso delegare qualsiasi logica personalizzata potrei avere all'interno della classe contenuta (es) setter di proprietà/getter, semplicemente utilizzando le proprietà della classe contenuta quando si accede alle proprietà della classe foglia. Poiché non c'è eredità, posso decorare tutte le proprietà della classe foglia con l'attributo XmlElementAttribute e utilizzare qualsiasi ordine che ritenga appropriato.


UPDATE:

sono tornato a rivisitare questo articolo, perché le mie decisioni di progettazione sull'utilizzo di ereditarietà di classe è tornato a mordere me ancora una volta. 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'oggetto 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, come penso che molti troveranno molto istruttivo e utile.

2

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 si rimuovono gli attributi XmlElement dalla classe base e si aggiunge 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 si spera quindi 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 potrebbero essere resi obbligatori:

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

Questo non è es sentoriale, ma significa che posso controllare tutto è 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 classe base), che ora realizzo logico.

Questo si chiama nel solito modo, modificando una riga sopra:

public class SerializableObject : SerializableBase, ISerializableObjectEnsureProperties 

Ho provato solo questo in WCF, e hanno portato il concetto di serializzazione XML senza compilare, quindi se questo non lo fa lavoro, scuse, ma mi aspetterei che si comporti allo stesso modo - sono sicuro che qualcuno me lo faccia sapere se non ...

2

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

Il nome del metodo deve 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: ~

<Object> 
    <Property1></Property1> 
    <Property2></Property2> 
    <Property3></Property3> 
</Object> 

Il risultato quando si utilizza SerializableObject1: ~

<Object> 
    <Property1></Property1> 
    <Property3></Property3> 
</Object> 

Spero che questo aiuti molti altri!

+0

Funziona davvero. Tuttavia, hai sostanzialmente aggiunto Property2 in classi a cui non appartiene (anche se potrebbe non apparire nell'xml). – Ian1971

Problemi correlati