Utilice el atributo XmlInclude o SoapInclude para especificar tipos que no se conocen estáticamente

98

Tengo un problema muy extraño al trabajar con .NET XmlSerializer.

Tome las siguientes clases de ejemplo:

public class Order 
{
    public PaymentCollection Payments { get; set; }

    //everything else is serializable (including other collections of non-abstract types)
}

public class PaymentCollection : Collection<Payment>
{
}

public abstract class Payment 
{
    //abstract methods
}

public class BankPayment : Payment
{
    //method implementations
}

AFAIK, hay tres métodos diferentes para resolver el problema InvalidOperationExceptioncausado por el serializador que no conoce los tipos derivados de Payment.

1. Añadiendo XmlIncludea la Paymentdefinición de clase:

Esto no es posible debido a que todas las clases se incluyen como referencias externas sobre las que no tengo control.

2. Pasar los tipos de tipos derivados durante la creación de la XmlSerializerinstancia

No funciona.

3. Definición XmlAttributeOverridesde la propiedad de destino para anular la serialización predeterminada de la propiedad (como se explica en esta publicación SO )

Tampoco funciona ( XmlAttributeOverridessigue la inicialización).

Type bankPayment = typeof(BankPayment);

XmlAttributes attributes = new XmlAttributes();
attributes.XmlElements.Add(new XmlElementAttribute(bankPayment.Name, bankPayment));

XmlAttributeOverrides overrides = new XmlAttributeOverrides();
overrides.Add(typeof(Order), "Payments", attributes);

XmlSerializerEntonces se usaría el constructor apropiado .

NOTA: por no funciona me refiero a que se lanza InvalidOperationException( BankPaymentno se esperaba ... ).

¿Alguien puede arrojar algo de luz sobre el tema? ¿Cómo se procedería a depurar más el problema?

lsoliveira
fuente

Respuestas:

93

Esto funcionó para mí:

[XmlInclude(typeof(BankPayment))]
[Serializable]
public abstract class Payment { }    

[Serializable]
public class BankPayment : Payment {} 

[Serializable]
public class Payments : List<Payment>{}

XmlSerializer serializer = new XmlSerializer(typeof(Payments), new Type[]{typeof(Payment)});
bizl
fuente
15
Entonces, ¿el tipo base necesita conocer todas sus implementaciones? Esta no parece una buena solución. ¿No hay otra forma?
Alexander Stolz
2
@AlexanderStolz para la implementación genérica pasando un nuevo tipo mientras se crea un objeto XmlSerializable es la mejor solución. Como se mencionó, stackoverflow.com/a/2689660/698127
Aamol
39

Acabo de resolver el problema. Después de investigar un poco más, encontré esta publicación SO que cubre exactamente la misma situación. Me puso en el camino correcto.

Básicamente, es XmlSerializernecesario conocer el espacio de nombres predeterminado si las clases derivadas se incluyen como tipos adicionales. La razón exacta por la que esto tiene que suceder aún se desconoce, pero, aún así, la serialización está funcionando ahora.

lsoliveira
fuente
2

Estoy de acuerdo con bizl

[XmlInclude(typeof(ParentOfTheItem))]
[Serializable]
public abstract class WarningsType{ }

Además, si necesita aplicar esta clase incluida a un elemento de objeto, puede hacerlo así

[System.Xml.Serialization.XmlElementAttribute("Warnings", typeof(WarningsType))]
public object[] Items
{
    get
    {
        return this.itemsField;
    }
    set
    {
        this.itemsField = value;
    }
}
Hamit YILDIRIM
fuente
1

Simplemente hágalo en la Base, de esa manera cualquier niño puede ser serializado, menos código limpiador de código.

public abstract class XmlBaseClass  
{
  public virtual string Serialize()
  {
    this.SerializeValidation();

    XmlSerializerNamespaces XmlNamespaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });
    XmlWriterSettings XmlSettings = new XmlWriterSettings
    {
      Indent = true,
      OmitXmlDeclaration = true
    };

    StringWriter StringWriter = new StringWriter();

    XmlSerializer Serializer = new XmlSerializer(this.GetType());
    XmlWriter XmlWriter = XmlWriter.Create(StringWriter, XmlSettings);
    Serializer.Serialize(XmlWriter, this, XmlNamespaces);
    StringWriter.Flush();
    StringWriter.Close();

    return StringWriter.ToString();

  }

  protected virtual void SerializeValidation() {}
}

[XmlRoot(ElementName = "MyRoot", Namespace = "MyNamespace")]
public class XmlChildClass : XmlBaseClass
{
  protected override void SerializeValidation()
  {
    //Add custom validation logic here or anything else you need to do
  }
}

De esta manera, puede llamar a Serialize en la clase secundaria sin importar las circunstancias y aún así poder hacer lo que necesita antes de que el objeto Serialize.

A. Dady
fuente
0

En base a esto , pude resolver esto cambiando el constructor XmlSerializerque estaba usando en lugar de cambiar las clases.

En lugar de usar algo como esto (sugerido en las otras respuestas):

[XmlInclude(typeof(Derived))]
public class Base {}

public class Derived : Base {}

public void Serialize()
{
    TextWriter writer = new StreamWriter(SchedulePath);
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(List<Derived>));
    xmlSerializer.Serialize(writer, data);
    writer.Close();
}

Hice esto:

public class Base {}

public class Derived : Base {}

public void Serialize()
{
    TextWriter writer = new StreamWriter(SchedulePath);
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(List<Derived>), new[] { typeof(Derived) });
    xmlSerializer.Serialize(writer, data);
    writer.Close();
}
derekantrican
fuente