Click here to Skip to main content
15,068,879 members
Articles / Web Development / ASP.NET
Article
Posted 12 Aug 2009

Stats

70.7K views
768 downloads
21 bookmarked

XML Serialization of a Class Inherited from Generic List of type Interface

Rate me:
Please Sign up or sign in to vote.
4.63/5 (13 votes)
12 Aug 2009CPOL8 min read
An article on XML Serialization of a class inherited with a generic List of interface, i.e., List

Introduction

The article describes how to serialize an object of a class inherited from generic list of interface (i.e., List<InterfaceName>) using XML serialization. I've developed the sample project in ASP.NET 3.5 but the same code can be used in .NET 2.0.

Here, I'm assuming that you know the basic implementation of IXmlSerializable interface. IXmlSerializable interface exposes three methods, GetSchema(), ReadXML() and WriteXML(). We will return null from GetSchema() and add code to ReadXML() and WriteXML() function.

Background

There was a requirement to create a Workflow engine in one of the projects I was working on. This workflow engine (object of type WorkFlow class) contains workflow items and executes them one by one and then moves to the next.

Any item of workflow should contain Execute method and ExecutionCompleted event. So I created an interface named IWorkFlowItem which contains the Execute method and the ExecutionCompleted event plus several common properties. Initially, I required two workflow items, i.e., Page and Condition. I created these two by implementing IWorkFlowItem interface. Then I created a class WorkFlowItems by inheriting generic List<IWorkFlowItem> and added a member of this type to the main Workflow class.

Everything worked fine till one requirement came to serialize the workflow object and save it. It threw me an error when I tried to serialize it. In the beginning, I implemented the WorkFlowItems class with IXmlSerializer and put a dirty code to serialize the class in ReadXml() and WriteXml() methods. But, because the type of workflow items can increase to any extent, this solution was of no use. Finally, I came up with a solution and am sharing it with you.

I couldn't put the actual code and am putting here a different example, but it will give you the exact picture of my implementation of interface serialization.

Why XML Serializer Cannot Serialize an Interface?

Have a look at the definitions of the classes used for this example:

Image 1

Image 2

Image 3

Image 4

Image 5

Now have a look at the use of project class:

Image 6

The above serialization code will throw an error because IProjectMember is not an object, it's an interface. An interface can only access members of the object by pointing to the same location in memory and it is limited to access only those members which are in scope of its own. That says the IProjectMember interface can only access Name, TotalHoursWorked and Responsibilities properties of an object that implements it.

Again, if somehow, we were able to serialize the properties that IProjectMember exposes, we would have got a serialized XML, something like this:

Image 7

The problem with the above serialization is:

  • We don't know what type of object an IProjectMember exposes. Is it a SoftwareDeveloper or a ProjectLeader?
  • We don't have additional properties specific to an object, that means while deserializing, we don't have actual state of the object.
  • On top-of-all, IProjectMember is an interface so you cannot create an instance of it. That means all the above points are null.

The Idea to Solve this Problem

We need an XML like given below to persist the actual state of the SoftwareProject object.

Image 8

The bad thing about an interface is, you cannot create an object out of it, but the good thing is that it points to an object (Instance of a class). Every object in .NET is, by default, inherited from object base class. I've always wondered why .NET has inherited every object from the base object class but it was a life saver for me in this case. The GetType() method of an object returns the actual type of that object. We will see that how I've used this method to solve this problem in 'Understanding the Code' section.

Ok, enough story, have a look at the ideas to serialize and deserialize the object/xml.

To serialize:

The idea is to get the exact type of object to which an interface item is pointing while iterating the list. Say, for the first interface item in the Members list, it is pointing to ProjectLeader object while the second object is pointing to SoftwareDeveloper object. After getting the type, cast that object to that type and serialize it and then write the serialized data on the XML writer stream in WriteXml() method.

To deserialize:

To deserialize, we need to convert the XML to an object. For this, the ReadXML() method of IXMLSerializable interface provides xmlReader parser. We will iterate through the items of SoftwareProject node. Each child node name represents the actual class name. While iterating through items, we can create a XMLSerializer to deserialize the data and can use Type.GetType(ClassName) to pass the type as input parameter to this XMLSerializer.

Understanding the Code

Files Description

  • IProjectMember.cs - Interface defining common properties/methods for a project member. The generic list of this interface will be serialized.
  • ProjectLeader.cs - Class to be used as a item for generic list to serialize.
  • SoftwareDeveloper.cs - Class to be used as a item for generic list to serialize.
  • ProjectTeam.cs - Class inherited from generic list of IProjectMember that is being serialized.
  • Project.cs - Class defining root object to hold ProjectTeam as a member to hold all the software developers and project leaders.
  • GenericListSerializer.cs - Class to XML serialize a generic list of type interface.
  • GenericListDeSerializer.cs - Class to XML deserialize a generic list of type interface.

Interface to Define a Project Member

C#
namespace SerializableList
{ 
	/// <summary>

	/// Interface to describe a project member 
	/// </summary> 
	public interface IProjectMember 
	{ 
		/// <summary>
		/// Name of the member 
		/// </summary>
		string Name { get; set; } 
		

		/// <summary> 
		/// Total number of hours the member has worked 
		/// </summary>
		int TotalHoursWorked { get; set; } 

		/// <summary> 
		/// Responsibilities the member has. 
		/// </summary> 
		string Responsibilities { get; set; } 
	} 
}

Project Team Class

C#
namespace SerializableList
{ 
	public class ProjectTeam : List<IProjectMember>, IXmlSerializable 
	{ 
	
		#region IXmlSerializable Members 

		public XmlSchema GetSchema() 
		{ return null; } 

		public void ReadXml(XmlReader reader) 
		{ 
			GenericListDeSerializer<IProjectMember> dslzr =
                               new GenericListDeSerializer<IProjectMember>(); 
			dslzr.Deserialize(reader, this); 
		} 

		public void WriteXml(XmlWriter writer) 
		{ 
			GenericListSerializer<IProjectMember> serializers =
                               new GenericListSerializer<IProjectMember>(this); 
			serializers.Serialize(writer); 
		} 

		#endregion
	} 
}

To implement IXmlSerializable interface, the class should implement these three methods:

  1. GetSchema() - In the current case, I'm returning null.
  2. ReadXml() - This function is used to deserialize the object (converting XML to original object). In this function, I've created an object of GenericListDeSerializer<IProjectMember> and passed the current reader stream and current object to its Deserialize(..) method.
  3. WriteXml() - This function is used to serialize the object(converting the original object to XML). In this function, I've created an object of GenericListSerializer<IProjectMember> and then pass the current writer stream to its Serialize(..) method.

Serialization and Deserialization

To instantiate XmlSerializer object, we require to pass a Type as a constructor parameter. Now, in the current scenario, because the list will contain different type of objects, we need to keep a list of every type of XmlSerializer, so that the list item can be serialized/deserialized with that specific XmlSerializer.

In the sample application, I've used Dictionary<key,value> to hold a list of XmlSerializer. Here, type name is the key and XmlSerializer object is the value. The GetSerializerByTypeName(string typeName) is used to find and add XmlSerializer object from this Dictionary<key, value> object.

Generic List Serializer

C#
namespace SerializableList 
{ 
	/// <summary>
	/// Generic list serializer 
	/// </summary>
	public class GenericListSerializer<T> 
	{ 
		//To retain the serializer list that the T contains

		private List<T> _interfaceList; 

		//Serializers collection stored with type name
		private Dictionary<string, XmlSerializer> serializers; 

		/// <summary/>

		/// Creates a new instance of generic list serializer 
		/// </summary/>
		/// <param name="interfaceList">Generic list of interface type</param>
		public GenericListSerializer(List<T> interfaceList) 
		{ 
			_interfaceList = interfaceList; 
			InitializeSerializers(); 
		} 

		/// <summary>
		/// Initializes all the type of XML serializers the
                  /// generic list requires 
		/// </summary>
		private void InitializeSerializers() 
		{ 
			serializers = new Dictionary<string, XmlSerializer>(); 
			for (int index = 0; index < _interfaceList.Count; index++) 
			{ 

				//Add new serializer to the serializers list by
                                     //passing the class name 
				//Ignore if already added
				GetSerializerByTypeName(
                                        _interfaceList[index].GetType().FullName); 
			} 
		} 
		
		/// <summary>
		/// Serializes list 
		/// </summary>
		/// <param name="outputStream">Output stream to write the
                  /// serialized data</param>

		public void Serialize(XmlWriter outputStream) 
		{ 
			for (int index = 0; index < _interfaceList.Count; index++) 
			{ 
				//Get appropriate serializer

				GetSerializerByTypeName(
                                       _interfaceList[index].GetType().FullName).Serialize
				(outputStream, _interfaceList[index]); 
			} 
		} 

		/// <summary>
		/// Gets serializer by type name from internal XML serializers list 
		/// If specific serializers doesn't exists adds it and returns it 
		/// </summary>
		/// <param name="typeName">Class type name</param>

		/// <returns>XmlSerializer</returns>
		private XmlSerializer GetSerializerByTypeName(string typeName) 
		{ 
			XmlSerializer returnSerializer = null; 
			
			//Check if existing already

			if (serializers.ContainsKey(typeName)) 
			{ 
				returnSerializer = serializers[typeName]; 
			} 
			//If doesn't exist in list create a new one and add it to list
			if (returnSerializer == null) 
			{ 
				returnSerializer = new XmlSerializer(
                                         Type.GetType(typeName)); 
				serializers.Add(typeName, returnSerializer); 
			} 

			//Return the retrived XmlSerializer

			return returnSerializer; 
		} 
	} 
}

The GenericListSerializer<T> class exposes one constructor GenericListSerializer(List<T> interfaceList) and one public method - Serialize(XmlWriter outputStream).

GenericListSerializer(List<T> interfaceList) - The constructor accepts the generic list to be serialized as an input parameter and invokes InitializeSerializers() method to create a list of XmlSerializer.

Serialize(System.Xml.XmlWriter outputStream) - This is the main method which serializes the generic list when invoked. It iterates through each item of generic list, while iterating, it gets the class type name of current item with CurrentItem.GetType().Name method and passes this to GetSerializerByTypeName() method to retrive appropriate XmlSerializer object. After getting XmlSerializer, it then invokes Serialize method and passes main XmlWriter output stream and current item(of generic list) to serialize.

Generic List Deserializer

C#
namespace SerializableList 
{ 
	public class GenericListDeSerializer<t> 
	{ 
		//Serializers collection stored with type name
		private Dictionary<string, XmlSerializer> serializers; 

		public GenericListDeSerializer() 
		{ 
			serializers = new Dictionary<string, XmlSerializer>(); 
		} 

		/// <summary>
		/// Deserializes list 
		/// </summary>
		/// <param name="outputStream">Output stream to write the
                  /// serialized data </param>
		public void Deserialize(XmlReader inputStream, List<T> interfaceList) 
		{ 
			//Get the base node name of generic list of items of
                           // type IProjectMember

			string parentNodeName = inputStream.Name; 

			//Move to first child
			inputStream.Read(); 

			while (parentNodeName != inputStream.Name) 
			{ 
				XmlSerializer slzr = GetSerializerByTypeName(
                                        inputStream.Name); 
				interfaceList.Add((T)slzr.Deserialize(inputStream)); 
			} 
		}
        
		/// <summary>
		/// Gets serializer by type name from internal XML serializers list 
		/// If specific serializers doesn't exists adds it and returns it 
		/// </summary>
		/// <param name="typeName">Class type name</param>
		/// <returns>XmlSerializer</returns> 

		private XmlSerializer GetSerializerByTypeName(string typeName) 
		{ 
			XmlSerializer returnSerializer = null; 
			
			//Check if existing already
			if (serializers.ContainsKey(typeName)) 
			{ 
				returnSerializer = serializers[typeName]; 
			} 
			//If doesn't exist in list create a new one and add it to list 
			if (returnSerializer == null) 
			{ 
				returnSerializer = new XmlSerializer(Type.GetType(
                                         Type.GetType(this.GetType().Namespace + "." + 
                                         typeName)); 
				serializers.Add(typeName, returnSerializer); 
			} 

			//Return the retrived XmlSerializer

			return returnSerializer; 
		} 
	} 
}</t>

The GenericListDeSerializer<T> class exposes one public method - Deserialize(XmlReader inputStream, List<T> interfaceList).

Deserialize(System.Xml.XmlReader inputStream, List<T> interfaceList) - This method deserializes the serialized object. It accepts two input parameters, XmlReader input stream and the generic list object to which deserialized objects will be added.

When the main XmlSerializer passes XmlReader stream to ReadXml() method of ProjectTeam class, the reader's pointer is located at <Members> tag. It is the member property name of type ProjectTeam in SoftwareProject class. This acts as a root node for all items in generic list of type IProjectMember.

Image 9

  1. parentNodeName variable is used to hold the name of root node so that the deserializer can deserialize all the items until it reaches the end node, i.e., </Members>
  2. inputStream.Read(); forwards the reader position to first child node, i.e., <ProjectLeader>
  3. while (parentNodeName != inputStream.Name) keeps a track of the scope of <Members> node. When it reaches the end node, i.e., </Members>, this condition will return false and the loop will be terminated.
    1. XmlSerializer slzr = GetSerializerByTypeName(inputStream.Name); fetches the appropriate XmlSerializer from stream by passing the class name as a key.
    2. interfaceList.Add((T)slzr.Deserialize(inputStream)); adds the deserialized object to the passed generic list. Here, I'm casting the deserialized object to the IProjectMember interface. When slzr.Deserialize(...) gets invoked, it deserializes the current node, i.e., <ProjectLeader>/<SoftwareDeveloper> and advances the reader to the next child node.

Limitations

  1. There are no additional properties in ProjectTeam : List<IProjectMember> class, because for that, we may need to handle the processing manually. But for now, I've not focused on that.
  2. I've given all the sample code in this tutorial with the namespace. This is because the GenericListDeSerializer<T> class should be in same namespace, otherwise it will throw an error while invoking Type.GetType(string typeName). This is because the GetType requires a fully qualified assembly name in this case, it is SerializableList.ProjectLeader or SerializableList.SoftwareDeveloper. So with the help of this.GetType().Namespace property and child node name <ProjectLeader>/<SoftwareDeveloper> fully qualified name is being passed to Type.GetType(..) method.
  3. I used Type.GetType().FullName property to get fully qualified assembly name of the type because while serializing the object, it throws an error if only class name is passed.

History

  • Version 1.0

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Hemant__Sharma
Technical Lead L & T Infotech
India India
No Biography provided

Comments and Discussions

 
QuestionHere is the missing code Pin
Member 1454510211-Aug-19 11:27
MemberMember 1454510211-Aug-19 11:27 
QuestionThere is an error in the code. Pin
Member 1454510211-Aug-19 10:53
MemberMember 1454510211-Aug-19 10:53 
QuestionDeserializing Generic classes giving error Pin
sudeep kushwaha21-Nov-18 23:22
Membersudeep kushwaha21-Nov-18 23:22 
QuestionRemove <Members> Pin
dkosasih12-Oct-15 11:15
Memberdkosasih12-Oct-15 11:15 
Questionfor tree structure, i append a line like below Pin
joo jinho20-Oct-11 22:07
Memberjoo jinho20-Oct-11 22:07 
NewsRe: for tree structure, i append a line like below Pin
johnowl15-May-12 8:37
Memberjohnowl15-May-12 8:37 
GeneralMy vote of 5 Pin
ADOConnection17-Jul-10 0:06
MemberADOConnection17-Jul-10 0:06 
GeneralDeserializer Enhancements Pin
Member 224480111-Sep-09 1:46
MemberMember 224480111-Sep-09 1:46 
GeneralRe: Deserializer Enhancements Pin
Hemant__Sharma22-Sep-09 20:29
MemberHemant__Sharma22-Sep-09 20:29 
GeneralRe: Deserializer Enhancements Pin
Vikram Lele2-Jul-10 2:51
MemberVikram Lele2-Jul-10 2:51 
GeneralSerializer aur Generic yai Kaab sikha tha Don.... Pin
abhi4u194713-Aug-09 3:41
Memberabhi4u194713-Aug-09 3:41 
GeneralGood tricks to serializer interface. Pin
Jayanta Kar13-Aug-09 2:03
MemberJayanta Kar13-Aug-09 2:03 
GeneralReally a nice article.... Pin
Barun Prasad12-Aug-09 20:38
MemberBarun Prasad12-Aug-09 20:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.