Self-Serialization - Serializing Custom Objects to XML in .NET

May 3, 2009
Sean Cooper

Here at Ideosity, we live and breathe XML. We use it heavily as a transport mechanism. While serializers built into .NET do a decent job of serializing objects into XML, we found that the way we want things just wasn't being met by the standard .NET XML SOAP serializers. In the interests of performance, we can skip a lot of the metadata that .NET generates.

There are articles out there about how to perform custom serialization. 4 Guys From Rollahas a nifty article on how to set up classes for custom serialization. For a couple reasons, we approached this topic a bit differently.

First of al, our solution involves much less typing, as in keystrokes, for larger objects. Secondly, our solution is naturally recursive. We think both of those are positives.

How Self-Serialization Works

Self-serialization is the ability of an object to render itself into a usable XML format. We can use the System.Reflection namespace to inspect the properties of a class, get their values, and put those values into an XML representation of the class.

If an object of type A has a property that is a collection of objects of type B and each B has a property that returns an instance of type C, our method will give you a nice, clean XML document with all objects serialized.

Where would we use something like this? Again the Guys from Rolla have a succint answer:

  • Storing user preferences in an object.
  • Maintaining security information across pages and applications.
  • Modification of XML documents without using the DOM.
  • Passing an object from one application to another.
  • Passing an object from one domain to another.
  • Passing an object through a firewall as an XML string.

An easy real world example is found when talking about HR issues. A company has N locations and each location has N number of employees.



<Company>
   <Locations>
      <Location>
         <Name>Main Office</Name>
         <Employees>
            <Employee>
               <Name>John Doe</Name>
               <EmpNumber>1</EmpNumber>
            </Employee>
            <Employee>
               <Name>Jane Doe</Name>
               <EmpNumber>2</EmpNumber>
            </Employee>
         </Employees>
      </Location>
      <Location>
         <Name>Sales Office</Name>
         <Employees>
            <Employee>
               <Name>Fred Johnson</Name>
               <EmpNumber>3</EmpNumber>
            </Employee>
            <Employee>
               <Name>Jane Phillips</Name>
               <Value>4</Value>
            </Employee>
         </Employees>
      </Location>
   </Locations>
</Company>

If we're smart, and we like to think we are, we can pull the company information, location information and personnel information from the database in a single XML document and use that document to inflate our custom objects where we can make modifications, like transferring an employee between offices.

We're going to transfer Jane to the Main Office.


Dim myEmp as Employee = Company.Locations(1).Employees(1) _
.TransferEmployee(myEmp, Company.Location(0), _
Company.Locations(1))

When we send the info back, we want an XML document that looks like this:
<Company>
      <Location>
         <Name>Main Office</Name>
         <Employees>
            <Employee>
               <Name>John Doe</Name>
               <EmpNumber>1</EmpNumber>
            </Employee>
            <Employee>
               <Name>Jane Doe</Name>
               <EmpNumber>2</EmpNumber>
            </Employee>
            <Employee>
               <Name>Jane Phillips</Name>
               <Value>4</Value>
            </Employee>            
         </Employees>
      </Location>
      <Location>
         <Name>Sales Office</Name>
         <Employees>
            <Employee>
               <Name>Fred Johnson</Name>
               <EmpNumber>3</EmpNumber>
            </Employee>
         </Employees>
      </Location>
   </Locations>
</Company>

With our custom objects, we can make multiple changes then send them all back to the database in one shot, using the same style of XML document we received the data in in the first place.

XML Serializer Code

In this example we will implement our serializer in a base class from which all objects inherit. This allows each object to render itself into XML, giving us flexibility as to what we send back to the databse.
To do this, we first need to import some namespaces. The functionality in the System.Reflection namespace is really what powers this method. The use of LINQ queries just makes the code a bit tidier.


Imports System.Collections.Generic
Imports System.Collections
Imports System.Xml
Imports System.Xml.Linq
Imports System.Reflection

Public MustInherit Class XMLBase

    Public Overridable Function ToXML(ByRef Doc As XmlDocument) As XmlNode
        Dim myNode As XmlNode = Doc.CreateElement(Me.GetType.Name)
        'use reflection to get the properties of the class
        Dim o As IEnumerable(Of PropertyInfo) = From o2 In _
                                Me.GetType.GetProperties Select o2
        Try
            For Each p As PropertyInfo In o

                Dim node2 As XmlNode = Doc.CreateElement(p.Name)
                If p.GetValue(Me, Nothing).GetType.IsSubclassOf( _
                    GetType(CMBase)) = True Then
                    ' serialize a class
                    Dim myObj As Object = p.GetValue(Me, Nothing)
                    node2.AppendChild(CType(myObj, CMBase).ToXML(Doc))
                Else
                    'serialize a value-type property
                    node2.InnerText = p.GetValue(Me, Nothing)
                    myNode.AppendChild(node2)
                End If
                myNode.AppendChild(node2)
            Next
        Catch ex As Exception
            'handle errors as you see fit
        End Try

        Return myNode
    End Function                                                
End Class

Now, let's look at the classes underlying our fictional HR system. In the interests of saving space, I've removed code not relevant to the topic.
Public Class Company
    Inherits XMLBase

    Private _locations As Locations

    Public Property Locations() As Locations
        Get
            Return _locations
        End Get
        Set(ByVal value As Locations)
            _locations = value
        End Set
    End Property
   
    ' This is one way to get our class serialized into XML
    Public Overloads Function ToXML() As System.Xml.XmlDocument
        Dim Doc As New XmlDocument
        MyBase.ToXML(Doc)
        Return Doc
    End Function
   
    Public Sub TransferEmployee(ByVal Emp As Employee, _
                ByVal Destination As Location, ByVal Source As Location)
        'do something here
    End Sub    
End Class

Public Class Location
    Inherits XMLBase
   
    Private _employees As Employees
    Private _name As String
   
    Public Property Name() As String
        Get
            Return _name
        End Get
        Set(ByVal value As String)
            _name = value
        End Set
    End Property
   
    Public Property Employees() As Employees
        Get
            Return _employees
        End Get
        Set(ByVal value As Employees)
            _employees = value
        End Set
    End Property    
End Class

Public Class Locations
    Private _collection As New List(Of Location)
   
    Public Function ToXML(ByRef Doc As XmlDocument) As XmlNode
        Dim node As XmlNode = Doc.CreateElement(Me.GetType.Name)
        For Each a As Object In Me._collection
        ' serialize each object in the collection to an XML node
            node.AppendChild(a.ToXML(Doc))
        Next
        Return node
    End Function
End Class

Public Class Employee
    Inherits XMLBase
   
    Private _name As String
    Private _employeeNumber As Integer
   
    Public Property Name() As String
        Get
            Return _name
        End Get
        Set(ByVal value As String)
            _name = value
        End Set
    End Property
   
    Public Property EmpNum() As Integer
        Get
            Return _employeeNumber
        End Get
        Set(ByVal value As Integer)
            _employeeNumber = value
        End Set
    End Property
End Class

Public Class Employees
    Private _collection As New List(Of Employee)
   
    Public Function ToXML(ByRef Doc As XmlDocument) As XmlNode
        Dim node As XmlNode = Doc.CreateElement(Me.GetType.Name)
        For Each a As Object In Me._collection
            node.AppendChild(a.ToXML(Doc))
        Next
        Return node
    End Function
End Class

Calling the ToXML method of our Company class starts a process of traversing down the tree. Each property is inspected to see if the return type inherites from the XMLBase class or if it's a value type. If the property returns a value that is a subclass of XMLBase, then a node is added to the current parent node and the ToXML method is called on that object and the process starts again until it reaches the "leaf" or value-type properties. Once a value-type property is found, a node is added to the current parent. Throughout the process, a single XMLDocument object is passed by reference to ensure our nodes are all being added to the same document.

There are two things I'd like to point out:
1) Rather than simply declaring our collections (Employees, Locations) as List(of T), we've created a class with a private collection. We handle our collections in this manner simply so we can loop through the collection and render each item in the collection.
2)The ToXML function is overloaded in the company class. We overload the ToXML method to make for quicker coding within the page. Now we can simply write:

WebService.DoUpdate(Company.ToXML)

rather than:

Dim doc as new XMLDocument
Doc.AppendChild(Company.ToXML(doc))
WebService.DoUpdate(Doc)



It's a small difference and your mileage may vary depending on circumstances. The point of this was to show how a single method within a base class can be used to serialize custom objects into XML. Enjoy.