Use o atributo XmlInclude ou SoapInclude para especificar tipos que não são conhecidos estaticamente

97

Eu tenho um problema muito estranho ao trabalhar com .NET XmlSerializer.

Faça as seguintes classes de exemplo:

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, existem três métodos diferentes para resolver o problema InvalidOperationExceptioncausado pelo serializador não saber sobre os tipos derivados de Payment.

1. Adicionando XmlIncludeà Paymentdefinição da classe:

Isso não é possível porque todas as classes foram incluídas como referências externas sobre as quais não tenho controle.

2. Passando os tipos dos tipos derivados durante a criação da XmlSerializerinstância

Não funciona.

3. Definição XmlAttributeOverridesda propriedade de destino para substituir a serialização padrão da propriedade (conforme explicado nesta postagem do SO )

Também não funciona (a XmlAttributeOverridesinicialização segue).

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

O XmlSerializerconstrutor apropriado seria então usado.

NOTA: por não funcionar quero dizer que o InvalidOperationException( BankPaymentnão era esperado ... ) é jogado.

Alguém pode lançar alguma luz sobre o assunto? Como alguém faria para depurar o problema ainda mais?

lsoliveira
fonte

Respostas:

92

Isso funcionou para mim:

[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
fonte
15
Portanto, o tipo base precisa saber todas as suas implementações? Esta não parece uma solução muito boa. Não há outra maneira?
Alexander Stolz
2
@AlexanderStolz para implementação genérica, passando um novo tipo durante a criação de objeto XmlSerializable é a melhor solução. Conforme mencionado stackoverflow.com/a/2689660/698127
Aamol
39

Apenas resolveu o problema. Depois de vasculhar um pouco mais, encontrei este post do SO que cobre exatamente a mesma situação. Isso me colocou no caminho certo.

Basicamente, é XmlSerializernecessário saber o namespace padrão se as classes derivadas forem incluídas como tipos extras. O motivo exato pelo qual isso aconteceu ainda é desconhecido, mas, ainda assim, a serialização está funcionando agora.

lsoliveira
fonte
2

Eu concordo com o bizl

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

também se você precisar aplicar esta classe incluída a um item de objeto, você pode fazer assim

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

Basta fazer na Base, assim qualquer filho pode ser Serializado, menos código mais limpo.

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

Dessa forma, você pode chamar Serialize na classe filha, não importa a circunstância e ainda ser capaz de fazer o que você precisa antes de objeto Serializes.

A. Dady
fonte
0

Com base nisso , consegui resolver isso alterando o construtor XmlSerializerque estava usando em vez de alterar as classes.

Em vez de usar algo assim (sugerido nas outras respostas):

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

Eu fiz isso:

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
fonte