Використовуйте атрибут XmlInclude або SoapInclude, щоб вказати типи, які не відомі статично


98

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

Хтось може пролити світло на цю тему? Як можна було б займатись і налагоджувати проблему далі?

Відповіді:


93

Це працювало для мене:

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

15
Отже, базовий тип повинен знати всі його реалізації? Це здається не дуже вдалим рішенням. Чи немає іншого шляху?
Олександр Штольц

2
@AlexanderStolz для загальної реалізації, передаючи новий тип під час створення XmlSerializable Object - найкраще рішення. Як уже згадувалося stackoverflow.com/a/2689660/698127
Aamol

39

Щойно вирішив проблему. Покопавшись трохи довше, я знайшов цю публікацію SO, яка висвітлює точно таку ж ситуацію. Це поставило мене на правильний шлях.

В основному, XmlSerializerпотрібно знати простір імен за замовчуванням, якщо похідні класи включені як додаткові типи. Точна причина, чому це повинно статися, досі невідома, але все ж серіалізація працює зараз.


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

1

Просто зробіть це в базі, таким чином, будь-яка дочірня може бути серіалізована, за винятком коду для очищення коду.

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


0

На основі цього я зміг вирішити це, змінивши конструктор, який 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();
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.