Użyj atrybutu XmlInclude lub SoapInclude, aby określić typy, które nie są znane statycznie

99

Mam bardzo dziwny problem podczas pracy z .NET XmlSerializer.

Weź następujące przykładowe zajęcia:

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, istnieją trzy różne metody rozwiązania problemu InvalidOperationExceptionspowodowanego brakiem wiedzy serializatora o typach pochodnych Payment.

1. Dodanie XmlIncludedo Paymentdefinicji klasy:

Nie jest to możliwe, ponieważ wszystkie klasy są uwzględnione jako odwołania zewnętrzne, nad którymi nie mam kontroli.

2. Przekazywanie typów typów pochodnych podczas tworzenia XmlSerializerinstancji

Nie działa.

3. Definiowanie XmlAttributeOverrideswłaściwości docelowej w celu zastąpienia domyślnej serializacji właściwości (jak wyjaśniono w tym poście SO )

Również nie działa ( XmlAttributeOverridesnastępuje inicjalizacja).

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

XmlSerializerNastępnie zostanie użyty odpowiedni konstruktor.

UWAGA: przez nie działa, mam na myśli, że InvalidOperationException( BankPaymentnie oczekiwano ... ) jest rzucany.

Czy ktoś może rzucić trochę światła na ten temat? Jak można by zająć się i dalej debugować problem?

lsoliveira
źródło

Odpowiedzi:

94

To zadziałało dla mnie:

[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
źródło
16
Więc typ podstawowy musi znać wszystkie swoje implementacje? To nie wydaje się być dobrym rozwiązaniem. Czy nie ma innego wyjścia?
Alexander Stolz,
3
@AlexanderStolz dla ogólnej implementacji przekazującej nowy typ podczas tworzenia obiektu XmlSerializable jest najlepszym rozwiązaniem. Jak wspomniano stackoverflow.com/a/2689660/698127
Aamol,
39

Właśnie rozwiązałem problem. Po dłuższym kopaniu znalazłem ten wpis SO, który obejmuje dokładnie tę samą sytuację. To doprowadziło mnie na właściwy tor.

Zasadniczo XmlSerializernależy znać domyślną przestrzeń nazw, jeśli klasy pochodne są uwzględnione jako dodatkowe typy. Dokładny powód, dla którego musi się to zdarzyć, jest nadal nieznany, ale nadal serializacja działa teraz.

lsoliveira
źródło
2

Na tej podstawie mogłem to rozwiązać, zmieniając konstruktora, z XmlSerializerktórego korzystałem, zamiast zmieniać klasy.

Zamiast używać czegoś takiego (sugerowanego w innych odpowiedziach):

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

Ja to zrobiłem:

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
źródło
2

Zgadzam się z bizl

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

także jeśli potrzebujesz zastosować tę klasę dołączoną do obiektu, możesz to zrobić w ten sposób

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

Po prostu zrób to w Base, w ten sposób każde dziecko może zostać zserializowane, mniej czystszy kod.

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

W ten sposób możesz wywołać Serialize na klasie podrzędnej bez względu na okoliczności i nadal być w stanie zrobić to, czego potrzebujesz, zanim obiekt Serializuje.

A. Dady
źródło