1

Problem

I am trying to XML serialize the class "ProfileSerialize" but I get this inner exception when calling xsSubmit.Serialize(writer, obj) in the Serialize function shown below.

Exception

Message = "The type ProfileFormatNumber was not expected. Use the XmlInclude or SoapInclude attribute to specify types that are not known statically."

What I already tried

I tried to implement the solutions mentioned in these threads:

  1. How to serialize derived classes in a Xml?
  2. Using XmlSerializer to serialize derived classes

Reproducible example

Imports System
Imports System.Collections.Generic
Imports System.Collections
Imports System.IO
Imports System.Text
Imports System.Linq
Imports System.Reflection
Imports System.Xml.Serialization
Imports System.XML

' https://stackoverflow.com/questions/72856211/why-does-xmlserializer-fail-to-serialize-this-class-even-though-i-added-the-xmli

<XmlInclude(GetType(ProfileFormatNumber))>
Public Class ProfileFormat
    <XmlElement(ElementName:="e1")>
    Public Property Name As String = String.Empty      
End Class

Public Class ProfileFormatNumber
    Inherits ProfileFormat

    <XmlElement(ElementName:="e1")>
    Public Property Divider As Integer = 1
End Class

Public Class Serializer(Of T)
    Public Shared Function Serialize(ByVal obj As T) As String

            Dim xsSubmit As XmlSerializer = New XmlSerializer(GetType(T))

            Using sww = New StringWriter()

                Using writer As XmlTextWriter = New XmlTextWriter(sww) With {.Formatting = Formatting.Indented}
                    xsSubmit.Serialize(writer, obj)
                    Return sww.ToString() ' I would recommend moving this out of the inner Using statement to guarantee the XmlWriter is flushed and closed
                End Using

            End Using

    End Function
End Class
                
Public Module Module1
    Public Sub Main()
        Dim profileFormat = New ProfileFormatNumber With { .Name = "my name", .Divider = 111 }

        Dim xml2 = Serializer(Of ProfileFormat).Serialize(profileFormat)
        Console.WriteLine(xml2)

    End Sub
End Module

My question

How do I need to modify my code to correctly use the <XmlInclude(GetType())> attribute? I tried adding it in multiple places but always receive the same exception.

dbc
  • 104,963
  • 20
  • 228
  • 340
chriscode
  • 121
  • 1
  • 8
  • @dbc thanks for the dotnetfiddle. I managed to write a minimal reproducible example. I now included my actual ElementNames and it turns out that they are causing the problem. Seems like I cannot use my naming logic when deriving classes? In the dotnetfiddle I got this very obvious exception "[System.InvalidOperationException: The XML element 'e1' from namespace '' is already present in the current scope. Use XML attributes to specify another XML name or namespace for the element.]". Do you want to add a brief answer? Thanks for putting me on the right track. – chriscode Jul 04 '22 at 17:45
  • Your problem is that you are trying to assign both properties `Divider` and `Name` (from the base class) of `ProfileFormatNumber` to the XML element name ``. You can't do that, they have to map to different names. This has nothing to do with `XmlInclude` by the way, [mcve] here: https://dotnetfiddle.net/UW1ozB. But what are you trying to accomplish here? – dbc Jul 04 '22 at 17:54
  • I use abbreviated element names to make it less obvious what is being serialized because I cannot obfuscate the classes (I include , otherwise it seems that XML serialization/deserialization does not work). – chriscode Jul 04 '22 at 18:02
  • You're welcome to use abbreviated element names, you just cannot use the **same name** for different properties in the same class (including its base classes). If you did, how would the serializer be able to deserialize? If I rename `ProfileFormatNumber.Divider` to `` then all is well, see https://dotnetfiddle.net/4LC9Nd – dbc Jul 04 '22 at 18:04
  • Yes, that's what I will do. Thanks for your help! Do you want to write a brief answer that I can accept? – chriscode Jul 04 '22 at 18:07

1 Answers1

3

Your problem is not with XmlInclude. Your problem is that you are trying to assign the two properties of ProfileFormatNumber, Divider and (from the base class) Name, to the same element name <e1> by setting <XmlElement(ElementName:="e1")> on both. I.e. if were able to serialize your ProfileFormatNumber as-is, the XML would look like:

<ProfileFormat xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="ProfileFormatNumber">
  <e1>my name</e1>
  <e1>111</e1>
</ProfileFormat>

However XmlSerializer does not support this (perhaps because there would be ambiguity in deserialization), hence the (slightly inscrutable) error complaining that the element name e1 is already present:

The XML element 'e1' from namespace '' is already present in the current scope. Use XML attributes to specify another XML name or namespace for the element.]

Instead, use a different obfuscated element name for ProfileFormatNumber.Divider such as <f1>:

<XmlInclude(GetType(ProfileFormatNumber))>
Public Class ProfileFormat
    <XmlElement(ElementName:="e1")>
    Public Property Name As String = String.Empty      
End Class

Public Class ProfileFormatNumber
    Inherits ProfileFormat

    <XmlElement(ElementName:="f1")>
    Public Property Divider As Integer = 1
End Class

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • Thanks for the explanation. Out of curiosity: do you have any idea why the fiddle is giving me a different exception than Visual Studio? I think the one in the fiddle is straightforward, whereas the VS exception is quite ambiguous. – chriscode Jul 04 '22 at 20:14
  • @chriscode - I'm sorry but I don't know why. The `XmlSerializer` code base is very old (dating from .Net 2.0 in 2006 I believe), and seems to be considered to be legacy technology by MSFT. It sometimes generates inscrutable or inappropriate error messages in edge cases. – dbc Jul 04 '22 at 20:30