Используйте атрибут XmlInclude или SoapInclude, чтобы указать типы, которые не известны статически

У меня очень странная проблема при работе с .NET XmlSerializer.

Возьмем следующие примеры классов:

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 существует три разных метода решения InvalidOperationException, вызванных сериализатором, не зная о производных типах Payment.

1. Добавление XmlInclude в определение класса Payment:

Это невозможно из-за того, что все классы включены в качестве внешних ссылок, над которыми я не контролирую.

2. Передача типов производных типов при создании экземпляра XmlSerializer

Не работает.

3. Определение XmlAttributeOverrides для целевого свойства, чтобы переопределить сериализацию по умолчанию свойства (как описано в этой странице SO)

Также не работает (инициализация XmlAttributeOverrides).

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

Затем будет использоваться соответствующий конструктор XmlSerializer.

ПРИМЕЧАНИЕ: не работает Я имею в виду, что InvalidOperationException (BankPayment не ожидалось...) выбрано.

Может ли кто-нибудь пролить свет на эту тему? Как можно продолжить и отладить проблему?

Ответ 1

Просто решил проблему. После копания еще некоторое время я нашел этот SO сообщение, которое охватывает ту же ситуацию. Это привело меня к правильному пути.

В принципе, XmlSerializer должен знать, что по умолчанию пространство имен , если производные классы включены как дополнительные типы. Точная причина, по которой это должно произойти, пока неизвестно, но, тем не менее, сериализация теперь работает.

Ответ 2

Это сработало для меня:

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

Ответ 3

Основываясь на этом, я смог решить эту проблему, изменив конструктор XmlSerializer я использовал вместо изменения классов.

Вместо того, чтобы использовать что-то вроде этого (предлагается в других ответах):

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

Я сделал это:

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

Ответ 4

Просто сделайте это в Base, чтобы любой ребенок мог быть сериализованным, с меньшим количеством кода для очистки кода.

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

Таким образом, вы можете вызывать Serialize для дочернего класса независимо от обстоятельств и при этом иметь возможность делать то, что вам нужно, прежде чем объект Serialize.