26

I have the following classes

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

Now when I try to run the code I got an InvalidOperationException at last line saying that

The type XmlTest.C was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.

I know that adding a [XmlInclude(typeof(C))] attribute with [XmlRoot] would solve the problem. But I want to achieve it dynamically. Because in my project class C is not known prior to loading. Class C is being loaded as a plugin, so it is not possible for me to add XmlInclude attribute there.

I tried also with

TypeDescriptor.AddAttributes(typeof(AList), new[] { new XmlIncludeAttribute(c.GetType()) });

before

var type = typeof (AList);

but no use. It is still giving the same exception.

Does any one have any idea on how to achieve it?

Anindya Chatterjee
  • 5,824
  • 13
  • 58
  • 82

4 Answers4

35

Two options; the simplest (but giving odd xml) is:

XmlSerializer ser = new XmlSerializer(typeof(AList),
    new Type[] {typeof(B), typeof(C)});

With example output:

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

The more elegant is:

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

With example output:

<AList xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <b />
  <c />
</AList>

In either case you must cache and re-use the ser instance; otherwise you will haemorrhage memory from dynamic compilation.

Sam
  • 40,644
  • 36
  • 176
  • 219
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 1
    I tried this one but it is giving me this following exception "XmlRoot and XmlType attributes may not be specified for the type XmlTest.AList." Any idea why its is coming and how to resolve it? – Anindya Chatterjee Apr 22 '10 at 10:02
  • @Anindya - that is odd; the example "as is" works fine for me. What framework version are you using? – Marc Gravell Apr 23 '10 at 05:21
  • @Anindya - I've tried on 4.0 and 2.0 (which covers 3.0 and 3.5) and cannot reproduce. Can you give a more complete description of this? – Marc Gravell Apr 23 '10 at 05:25
  • 1
    I was going crazy looking for a property of `XmlAttributes` that accepts an `XmlIncludeAttribute`. I would have never thought of using an `XmlElementAttribute` instead. Thanks. – Allon Guralnek Aug 20 '12 at 07:24
10

Building on Marc's first answer (I only have to read, so I don't need to prevent the weird output), I use a more dynamic/generic type-array to account for unknown types, inspired by this 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);
    }

(One could probably make it more efficient, e.g. using a static or read-only type-array in stead of a local variable. That would avoid repeatedly using Reflection. But I don't know enough about when assemblies get loaded and classes and properties get initialized, to know if that would get you into trouble. My usage is not that much, to take the time to investigate this all, so I just use the same Reflection multiple times.)

Yahoo Serious
  • 3,728
  • 1
  • 33
  • 37
2

Have a look at the documentation of XmlSerializer. There is a constructor which expects known types as the second parameter. That should work fine for you use case.

Achim
  • 15,415
  • 15
  • 80
  • 144
0

I'm don't think attributes can be applied at runtime, as they are used to create Meta-data at the CIL code.

M.A. Hanin
  • 8,044
  • 33
  • 51