2009-06-19 7 views
13

Estoy tratando de serializar algunos objetos usando XmlSerializer y la herencia, pero estoy teniendo algunos problemas para ordenar el resultado..NET Serialization Ordering

A continuación se muestra un ejemplo similar a lo que he fijado: ~

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

El resultado que quiero es el siguiente: ~

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

Sin embargo estoy recibiendo un resultado de: ~

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

¿Alguien sabe si es posible o de alguna alternativa?

Gracias

+0

Tuve un problema similar al que necesitaba la propiedad de la clase derivada para aparecer al final en el mensaje SOAP, mi solución era agregar la propiedad como interna en la clase base, y luego ocultarla con la palabra clave "nueva" en la clase derivada. Vea mi respuesta [aquí] (http://stackoverflow.com/questions/22174311/wcf-serialization-order-issue/22177272#22177272). Espero eso ayude. –

Respuesta

3

Parece que la clase XmlSerializer serializa el tipo base y luego deriva tipos en ese orden y sólo está respetando la propiedad de orden dentro de cada clase individual. A pesar de que el orden no es totalmente lo que quieres, aún debería deserializarse correctamente. Si realmente debe tener el pedido así, tendrá que escribir un serializador xml personalizado. Advertiría en contra de eso porque .NET XmlSerializer hace un manejo especial para usted. ¿Puedes describir por qué necesitas las cosas en el orden que mencionas?

3

EDITAR: Este enfoque no funciona. Dejé la publicación para que las personas puedan evitar esta línea de pensamiento.

El serializador actúa recursivamente. Hay un beneficio en esto; en la deserialización, el proceso de deserialización puede leer la clase base, luego la clase derivada. Esto significa que una propiedad en la clase derivada no se establece antes de las propiedades en la base, lo que podría generar problemas.

Si lo que realmente importa (y no estoy seguro de por qué es importante para obtener estos en orden), entonces puede probar este -

1) poner a la clase base Propiedad1 y Propiedad3 virtual. 2) anularlos con propiedades triviales en su clase derivada. Por ejemplo,

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

} 

Esto coloca una implementación concreta de la propiedad en la clase más derivada, y se debe respetar el orden.

+0

Intenté esto - realmente no funcionó –

+0

Ah, lo siento - Hubiera pensado que había funcionado observando la clase que contenía una implementación concreta, pero claramente no. Actualizaré la publicación para indicar que este enfoque no funciona. –

+0

todavía era una buena idea :) –

15

Técnicamente, desde una perspectiva de xml puro, diría que esto es probablemente algo malo que desear hacer.

.NET oculta gran parte de la complejidad de elementos como XmlSerialization; en este caso, oculta el esquema al que debe ajustarse su xml serializado.

El esquema inferido utilizará elementos de secuencia para describir el tipo de base y los tipos de extensión. Esto requiere un orden estricto, incluso si el deserializador es menos estricto y acepta elementos fuera de servicio.

En los esquemas xml, al definir tipos de extensión, los elementos adicionales de la clase secundaria deben venir después de los elementos de la clase base.

que sería esencialmente tener un esquema que se ve algo como (etiquetas XML-y eliminadas para mayor claridad)

base 
    sequence 
    prop1 
    prop3 

derived1 extends base 
    sequence 
    <empty> 

derived2 extends base 
    sequence 
    prop2 

No hay ninguna manera de pegar un marcador de posición en el medio prop1 y PROP3 para indicar donde las propiedades de la derivada xml puede ir.

Al final, tiene una falta de coincidencia entre su formato de datos y su objeto comercial. Probablemente su mejor alternativa es definir un objeto para tratar con su serialización xml.

Por ejemplo

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

Esto separa el código de serialización XML a partir de su modelo de objetos. Copie todos los valores de SerializableObject1 o SerializableObject2 a SerializableObjectForPersistance, y luego serialícelos.

Básicamente, si desea un control específico sobre el formato de su xml serializado que no coincide exactamente con el marco de serialización de xml de expectativas, debe desacoplar el diseño de objeto de negocio (estructura de herencia en este caso) y la responsabilidad para la serialización de ese objeto comercial.

+0

+1, el esquema es un punto importante pero que se pasa por alto fácilmente. – shambulator

+1

Hace una buena observación sobre por qué el orden no se comporta "como se esperaba", porque estamos pensando en términos de OOP y no de esquemas XML. En mi caso particular, el acoplamiento suelto no es un diseño apropiado (de nuevo, este es un nicho - ¡siempre se esfuerza por un acoplamiento flexible!), Y si esa es su situación, siempre puede intentar la agregación, donde el objeto "niño" _contiene_ el objeto "padre" Aún logra la encapsulación y reutilización, pero también puede especificar, para el niño, el orden exacto de los elementos. – fourpastmidnight

+0

@Nader Hice referencia a su respuesta en mi respuesta a esta pregunta. Mientras mi solución funciona, la tuya es claramente superior. Estaba equivocado al decir que el acoplamiento flojo no era apropiado en mi diseño. Si alguna vez tengo la oportunidad de refactorizar, ¡estoy usando tus recomendaciones! – fourpastmidnight

0

Como dijo Nader, tal vez piense en hacer un diseño más suelto. Sin embargo, en mi caso, el acoplamiento suelto no era apropiado. Aquí está mi jerarquía de clases, y cómo propongo resolver el problema sin usar serialización personalizada o DTOs.

En mi proyecto, estoy construyendo un montón de objetos para representar piezas de un documento XML que se enviarán a través de un servicio web. Hay una gran cantidad de piezas. No todos se envían con cada solicitud (en realidad, en este ejemplo, estoy modelando una respuesta, pero los conceptos son los mismos). Estas piezas se usan como bloques de construcción para armar una solicitud (o desmontar una respuesta, en este caso). Así que aquí hay un ejemplo de uso de agregación/encapsulación para lograr el orden deseado a pesar de la jerarquía de herencia.

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

Así, mientras que tengo que crear propiedades para todas las clases de contenidos, puedo delegar toda la lógica propia que podría tener dentro de las clases contenido (en) los fijadores de propiedad/captadores simplemente usando las propiedades de la clase contenida cuando se accede a las propiedades de la clase hoja. Como no hay herencia, puedo decorar todas las propiedades de la clase de hoja con el atributo XmlElementAttribute y usar cualquier orden que considere adecuada.


ACTUALIZACIÓN:

volví a visitar este artículo porque mis decisiones de diseño sobre el uso de la herencia de clases volvieron a morder de nuevo. Si bien mi solución anterior funciona, la estoy usando, realmente creo que la solución de Nader es la mejor y debería considerarse antes de la solución que presenté. De hecho, lo estoy haciendo +1 hoy! Realmente me gusta su respuesta, y si alguna vez tengo la oportunidad de refactorizar mi proyecto actual, definitivamente separaré el objeto comercial de la lógica de serialización para objetos que de otra manera se beneficiarían enormemente de la herencia para simplificar el código y hacerlo más fácil. para que otros lo usen y entiendan.

Gracias por publicar su respuesta Nader, ya que creo que a muchos les resultará muy instructivo y útil.

2

Esta publicación es bastante antigua ahora, pero recientemente tuve un problema similar en WCF, y encontré una solución similar a la anterior de Steve Cooper, pero una que funciona, y presumiblemente también funcionará para la serialización XML.

Si elimina los atributos XmlElement de la clase base y agrega una copia de cada propiedad con un nombre diferente a las clases derivadas que acceden al valor base a través del get/set, las copias se pueden serializar con el nombre apropiado asignado el uso de un XmlElementAttribute, y se espera que a continuación serializar en el orden predeterminado:

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

también he añadido una interfaz para añadir a las clases derivadas, por lo que las copias pueden ser obligatorios:

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

Esto no es es sential, pero significa que puedo verificar que todo esté implementado en tiempo de compilación, en lugar de verificar el XML resultante. Originalmente había hecho estas propiedades abstractas de SerializableBase, pero estas se serializan primero (con la clase base), y ahora me doy cuenta de que es lógico.

esto se le llama en la forma habitual cambiando una línea anterior:

public class SerializableObject : SerializableBase, ISerializableObjectEnsureProperties 

sólo lo he probado esto en WCF, y han portado el concepto de serialización XML sin compilar, así que si esto no lo hace trabajo, disculpas, pero esperaría que se comporte de la misma manera; estoy seguro de que alguien me avisará si no ...

2

Sé que esta pregunta ha expirado; sin embargo, aquí hay una solución para este problema:

El nombre del método siempre debe comenzar con ShouldSerialize y luego finalizar con el nombre de la propiedad. Entonces simplemente necesita devolver un booleano basado en el condicional que desee, ya sea para serializar el valor o no.

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

El resultado al utilizar SerializableObject2: ~

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

El resultado al utilizar SerializableObject1: ~

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

espero que esto ayude muchos otros!

+0

Esto realmente funciona. Sin embargo, esencialmente ha agregado Property2 en clases a las que no pertenece (aunque podría no aparecer en el xml). – Ian1971

Cuestiones relacionadas