dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.06k stars 4.69k forks source link

XmlSerializer does not respect member type specified in XmlElement attribute when DynamicCodeSupport is false #108432

Open ivanpovazan opened 2 days ago

ivanpovazan commented 2 days ago

Description

When there are multiple XmlElement attributes attached to a property, and one of them specifies that the member type is of some base type A, serializing an object whose property is initialized with type B, where B : A, throws with:

Unhandled exception. System.InvalidOperationException: There was an error generating the XML document.
 ---> System.InvalidOperationException: The type B was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.

This was reported to us by a customer who experienced the problem when targeting iOS platforms with .NET as the DynamicCodeSupport feature switch is false by default on these platforms on all supported runtimes: NativeAOT and MonoAOT.

For reference, I created a console app reproduction which shows that this only happens when DynamicCodeSupport is set to false.

Repro

If we consider the following example:

using System.Xml.Serialization;

var container = new Container
{
    Items = new()
    {
        new B()
    }
};

XmlSerializer serializer = new XmlSerializer(typeof(Container));
using (StringWriter writer = new StringWriter())
{
    serializer.Serialize(writer, container);
    string xmlString = writer.ToString();
    Console.WriteLine(xmlString);
}

[XmlInclude(typeof(B))]
public class A { }
public class B : A { }
public class C { }

public class Container
{
    [XmlElement("As", typeof(A))]
    [XmlElement("Cs", typeof(C))]
    public List<object> Items { get; set; }
}

and build/run it as a console application with dotnet run , the output is as follows:

<?xml version="1.0" encoding="utf-16"?>
<Container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <As xsi:type="B" />
</Container>

When we rebuild and rerun the application with DynamicCodeSupport set to false ie: dotnet run -p:DynamicCodeSupport=false the app throws with:

Unhandled exception. System.InvalidOperationException: There was an error generating the XML document.
 ---> System.InvalidOperationException: The type B was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically.
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteElements(Object o, ElementAccessor[] elements, TextAccessor text, ChoiceIdentifierAccessor choice, Boolean writeAccessors, Boolean isNullable)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteArrayItems(ElementAccessor[] elements, TextAccessor text, ChoiceIdentifierAccessor choice, Object o)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteStructMethod(StructMapping mapping, String n, String ns, Object o, Boolean isNullable, Boolean needType)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.WriteElements(Object o, ElementAccessor[] elements, TextAccessor text, ChoiceIdentifierAccessor choice, Boolean writeAccessors, Boolean isNullable)
   at System.Xml.Serialization.ReflectionXmlSerializationWriter.GenerateTypeElement(Object o, XmlTypeMapping xmlMapping)
   at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
   --- End of inner exception stack trace ---
   at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
   at Program.<Main>$(String[] args) in /Users/ivan/tmp/net8/MultipleXmlElementsWithDerivedTypes/Program.cs:line 14

Additional notes

This is probably similar issue to https://github.com/dotnet/runtime/issues/107252 where we use ReflectionOnly serialization.

FWIW, when we keep only one XmlElement attribute (relevant for the test case) attached to the property, serialization works fine even with DynamicCodeSupport=false.

public class Container
{
    [XmlElement("As", typeof(A))]
-    [XmlElement("Cs", typeof(C))]
    public List<object> Items { get; set; }
}

Known workarounds when targeting iOS platforms

Enable interpreter by adding the following in the project file:

<UseInterpreter>true</UseInterpreter>
dotnet-policy-service[bot] commented 2 days ago

Tagging subscribers to 'os-ios': @vitek-karas, @kotlarmilos, @ivanpovazan, @steveisok, @akoeplinger See info in area-owners.md if you want to be subscribed.