Используйте атрибут 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.