Как динамически добавить атрибут XmlInclude - PullRequest
25 голосов
/ 22 апреля 2010

У меня есть следующие классы

[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 ]

33 голосов
/ 22 апреля 2010

Два варианта; самый простой (но нечетный 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; в противном случае вы потеряете память из-за динамической компиляции.

8 голосов
/ 18 января 2013

Основываясь на первом ответе Марка (мне нужно только прочитать, поэтому мне не нужно предотвращать странный вывод), я использую более динамический / универсальный массив типов для учета неизвестных типов, вдохновленный этим CodeProject .

    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. Но я не знаю достаточно о том, когда сборки загружаются и классы и свойства инициализируются, чтобы знать, не вызовет ли это у вас неприятности. Я использую не так уж много, чтобы потратить время на изучение всего этого, поэтому я просто использую одно и то же Отражение несколько раз.)

2 голосов
/ 22 апреля 2010

Ознакомьтесь с документацией XmlSerializer.Есть конструктор, который ожидает известные типы как второй параметр.Это должно хорошо работать в вашем случае.

0 голосов
/ 22 апреля 2010

Я не думаю, что атрибуты могут применяться во время выполнения, так как они используются для создания метаданных в коде CIL.

...