Как динамически добавить атрибут XmlInclude
У меня есть следующие классы
[XmlRoot]
public class AList
{
public List<B> ListOfBs {get; set;}
}
public class B
{
public string BaseProperty {get; set;}
}
public class C : B
{
public string SomeProperty {get; set;}
}
public class Main
{
public static void Main(string[] args)
{
var aList = new AList();
aList.ListOfBs = new List<B>();
var c = new C { BaseProperty = "Base", SomeProperty = "Some" };
aList.ListOfBs.Add(c);
var type = typeof (AList);
var serializer = new XmlSerializer(type);
TextWriter w = new StringWriter();
serializer.Serialize(w, aList);
}
}
Теперь, когда я пытаюсь запустить код, в последней строке я получил InvalidOperationException, говорящий, что
Тип XmlTest.C не ожидался. Используйте атрибут XmlInclude или SoapInclude, чтобы указать типы, которые не известны статически.
Я знаю, что добавление атрибута [XmlInclude(typeof(C))] с помощью [XmlRoot] решит проблему. Но я хочу добиться этого динамически. Потому что в моем проекте класс C не известен до загрузки. Класс C загружается как плагин, поэтому я не могу добавить туда атрибут XmlInclude.
Я пробовал также с
TypeDescriptor.AddAttributes(typeof(AList), new[] { new XmlIncludeAttribute(c.GetType()) });
до
var type = typeof (AList);
но бесполезно. Это все еще дает то же исключение.
Есть ли у кого-нибудь идеи о том, как этого добиться?
4 ответа
Два варианта; самый простой (но нечетный xml):
XmlSerializer ser = new XmlSerializer(typeof(AList),
new Type[] {typeof(B), typeof(C)});
С примером вывода:
<AList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ListOfBs>
<B />
<B xsi:type="C" />
</ListOfBs>
</AList>
Более элегантным является:
XmlAttributeOverrides aor = new XmlAttributeOverrides();
XmlAttributes listAttribs = new XmlAttributes();
listAttribs.XmlElements.Add(new XmlElementAttribute("b", typeof(B)));
listAttribs.XmlElements.Add(new XmlElementAttribute("c", typeof(C)));
aor.Add(typeof(AList), "ListOfBs", listAttribs);
XmlSerializer ser = new XmlSerializer(typeof(AList), aor);
С примером вывода:
<AList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<b />
<c />
</AList>
В любом случае вы должны кэшировать и повторно использовать ser
пример; в противном случае у вас будет кровотечение из динамической компиляции.
Основываясь на первом ответе Марка (мне нужно только прочитать, поэтому мне не нужно предотвращать странный вывод), я использую более динамический / универсальный массив типов для учета неизвестных типов, вдохновленный этим проектом кода.
public static XmlSerializer GetSerializer()
{
var lListOfBs = (from lAssembly in AppDomain.CurrentDomain.GetAssemblies()
from lType in lAssembly.GetTypes()
where typeof(B).IsAssignableFrom(lType)
select lType).ToArray();
return new XmlSerializer(typeof(AList), lListOfBs);
}
(Можно, вероятно, сделать его более эффективным, например, используя статический массив или массив типов только для чтения вместо локальной переменной. Это позволит избежать повторного использования Reflection. Но я не знаю достаточно о том, когда сборки загружаются, а также классы и свойства получить инициализацию, чтобы узнать, не вызовет ли это у вас проблемы. Я использую не так уж много времени, чтобы потратить время на изучение всего этого, поэтому я просто использую одно и то же Отражение несколько раз.)
Ознакомьтесь с документацией по XmlSerializer. Есть конструктор, который ожидает известные типы как второй параметр. Это должно работать нормально для вашего случая использования.
Я не думаю, что атрибуты могут применяться во время выполнения, так как они используются для создания метаданных в коде CIL.