Let's say i have two projects in Visual Studio. The Base project and a Derived project.

The base project contains a base class:

Public Class Car
    Property Name As String
End Class

and a generic class for serializing

Public Class Seriallizer(Of T)
Function SerializeObjToString(obj As T) As String
        Dim serializer As XmlSerializer
        Dim memStream As New MemoryStream
        Dim xmlWriter As New XmlTextWriter(memStream, Encoding.UTF8)
        Try
            serializer = New XmlSerializer(GetType(T))
            serializer.Serialize(xmlWriter, obj)
            Dim xml As String
            xml = Encoding.UTF8.GetString(memStream.GetBuffer)
            xml = xml.Substring(xml.IndexOf(Convert.ToChar(60)))
            xml = xml.Substring(0, (xml.LastIndexOf(Convert.ToChar(62)) + 1))
            Return xml
        Catch e As Exception
            MsgBox($"Error writing data to string: {e.Message}")
        Finally
            xmlWriter.Close()
            memStream.Close()
            xmlWriter = Nothing
            serializer = Nothing
            memStream = Nothing
        End Try
        Return ""
    End Function

Now i have derived class "SpecialCar" in my second project.

Public Class SpecialCar
    Inherits Car
    Sub New()
        MyBase.New
    End Sub
    Property VentorName As String
End Class

My project with the derived class has a reference to the base class project. When i now want to serialize the derived class i am getting an error:

Dim spezicalCar As New SpecialCar
Dim serializer As New Seriallizer(Of Car)
Dim str As String = serializer.SerializeObjToString(spezicalCar)

Error:

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

I know, i can use the XMLInclude attribute but this does not work in this case, as the base class project does not know about the derived class project. In other SO posts i read something about providing a xml namespace to classes but i don't know how to do that. How can i serialize my derived class to an XML?

1

There are 1 best solutions below

0
gegy On

After doing a day of research i found a solution for my question.

First you need to write your own serializer (implementing the IXmlSerializable interface) class (should be gerneric, that you can use it for all abstract (Base/MustInherit)) classes. The following SO and codeproject.com links helped me to solve the task:

XML Serialization and Inherited Types

https://www.codeproject.com/Articles/8644/XmlSerializer-and-not-expected-Inherited-Types

In both links you are going to find a similar solution. Be aware, in the codeproject link you have to look in the comment section for the generic solution.

So here is my final generic serializer class, which you can place in the project with the base class, or in an dedicated project and add a reference in the base class to the dedicated project.

Imports System.Xml
Imports System.Xml.Schema
Imports System.Xml.Serialization

Public Class GenericSerializer(Of T)
    Implements IXmlSerializable

    Public Shared Widening Operator CType(p As GenericSerializer(Of T)) As T
        Return IIf(p Is Nothing, Nothing, p.Derived)
    End Operator

    Public Shared Widening Operator CType(p As T) As GenericSerializer(Of T)
        Return IIf(p Is Nothing, Nothing, New GenericSerializer(Of T)(p))
    End Operator

    Private _derived As T
    Public ReadOnly Property Derived As T
        Get
            Return _derived
        End Get
    End Property

    Public Sub New()
        'Nothing to do here
    End Sub

    Public Sub New(ByVal derived As T)
        Me._derived = derived
    End Sub

    Public Sub ReadXml(reader As XmlReader) Implements IXmlSerializable.ReadXml
        Dim typeNameAttrib As String = reader.GetAttribute("Type")
        Dim assemblyNameAttrib As String = reader.GetAttribute("Assembly")
        ' Ensure the Type was Specified
        If (typeNameAttrib Is Nothing) Then
            Throw New ArgumentNullException(("Unable to Read Xml Data for Abstract Type '" _
                    + (GetType(T).Name + "' because no 'Type' attribute was specified in the XML.")))
        End If
        If (assemblyNameAttrib Is Nothing) Then
            Throw New ArgumentNullException(("Unable to Read Xml Data for Abstract Type '" _
                    + (GetType(T).Name + "' because no 'assembly' attribute was specified in the XML.")))
        End If

        Dim assembly As Reflection.Assembly
        assembly = AppDomain.CurrentDomain.GetAssemblies.FirstOrDefault(Function(a) a.FullName = assemblyNameAttrib)

        If assembly Is Nothing Then
            Throw New Exception($"Assembly with name {assemblyNameAttrib} not found in Appdomain.")
        End If

        Dim type As Type
        type = assembly.GetType(typeNameAttrib)


        ' Check the Type is Found.
        If (type Is Nothing) Then
            Throw New InvalidCastException(("Unable to Read Xml Data for Abstract Type '" _
                    + (GetType(T).Name + "' because the type specified in the XML was not found.")))
        End If

        If Not type.IsSubclassOf(GetType(T)) Then
            Throw New InvalidCastException(("Unable to Read Xml Data for Abstract Type '" _
                    + (GetType(T).Name + ("' because the Type specified in the XML differs ('" _
                    + (type.Name + "').")))))
        End If

        reader.ReadStartElement()
        Me._derived = New XmlSerializer(type).Deserialize(reader)
        reader.ReadEndElement()
    End Sub

    Public Sub WriteXml(writer As XmlWriter) Implements IXmlSerializable.WriteXml
        Dim type As Type = Me._derived.GetType
        writer.WriteAttributeString("Type", type.FullName)
        writer.WriteAttributeString("Assembly", type.Assembly.FullName)
        Call New XmlSerializer(_derived.GetType).Serialize(writer, _derived)
    End Sub

    Public Function GetSchema() As XmlSchema Implements IXmlSerializable.GetSchema
        Return Nothing
    End Function

End Class

Unfortunately i had to wrap the base class in another class to use the custom serializer.

Imports System.Xml.Serialization

Public Class Car
    Property Name As String
End Class

Public Class XMLCar
    <XmlElement(GetType(GenericSerializer(Of Car)), ElementName:="Car")>
    Property Car As Car
End Class

Call the serialzer like this:

Dim spezicalCar As New SpecialCar
Dim serializer As New Seriallizer
Dim xmlCar As New XMLCar With {.Car = spezicalCar}
Dim str As String = serializer.SerializeObjToString(Of XMLCar)(xmlCar)

You can also use it on lists of derived class objects:

<XmlArrayItem(GetType(GenericSerializer(Of Car)))>
Property CarList As List(Of Car)

EDIT 06.03.2024 I had an issue on reading the xml. For some reason the written type name was not found on reading. So i made a couple of changes in the code above. Now the assembly name and the type name are written in two xml attributes.