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

99

У меня очень странная проблема при работе с .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не ожидалось ... ) выбрасывается.

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

Исоливейра
источник

Ответы:

94

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

[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
источник
16
Значит, базовому типу нужно знать все его реализации? Это не кажется очень хорошим решением. Нет другого выхода?
Александр Штольц
3
@AlexanderStolz для общей реализации с передачей нового типа при создании объекта XmlSerializable - лучшее решение. Как уже упоминалось, stackoverflow.com/a/2689660/698127
Aamol
39

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

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

Исоливейра
источник
2

Исходя из этого, я смог решить эту проблему, изменив конструктор, который 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();
}
дерекантрианец
источник
2

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

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 для дочернего класса независимо от обстоятельств и по-прежнему иметь возможность делать то, что вам нужно, до сериализации объекта.

А. Дади
источник
2

Я согласен с bizl

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

также, если вам нужно применить этот включенный класс к объекту, вы можете сделать это

[System.Xml.Serialization.XmlElementAttribute("Warnings", typeof(WarningsType))]
public object[] Items
{
    get
    {
        return this.itemsField;
    }
    set
    {
        this.itemsField = value;
    }
}
Хамит ИЛДИРИМ
источник