You know object inheritance and polymorphism and you know they work. Recently I had an opportunity to work on a concept to verify the serialization and deserialization of objects/interfaces that are passed as by reference (and type casted to either of concrete base type or base interface type) to Remoting and WCF services.
Question was do we lose the fields off the subclass (derived class) during the serialization and deserialization? What about the integrity of by reference object?
In the world of business, you can’t rely on blogs and you must verify the assumptions. To do that you must intercept the messages at Remoting Sink and WCF Message Inspector to check the request/response. Good news is- you don’t lose any filed when you type cast a concrete subclass (derived class) to a concrete base class (or to a base interface)- as the book says. Intercepted request/response proves the theory. Microsoft’s RealProxy seamlessly map the Args back to referenced memory at the client.
The project implementation required an object container with an interface property. Everything seems to work fine and there is no serilization issue with BinaryFormatter (.Net Remoting), NetDataContractSerilizer or DataContractSerializer(WCF). Those serializer has no issue serializing interface property in the container object. We had a lot of use cases where the existing applications/libraries were using XmlSerializer for various purposes (i.e. applying xslt, message auditing, etc.) and it would be harder (high risk/high impact) to change them. When I tried to serialize the container object I was awarded with error- “Cannot serialize member .. of type .. because it is an interface.”. The error message is understandable and I tried to pass extra types to XmlSerializer but it still would complain!
So, how do you serialize the interface property that would not become maintenance nightmare?
Let’s take a look at the object hierarchy.
Here is the container class that I needed to xml serialize but received error:
using System; using System.Xml.Serialization; using System.Text; using System.Xml; using System.IO; using System.Xml.Linq; using System.Xml.XPath; using System.Linq; namespace Concept { [Serializable] public class ApplicationInterfaceContainer { public IBaseLoanApplication Application { get; set; } public string Comments { get; set; } } }
After few internet search I was convinced with a solution provided by shuggycouk at stackoverflow.com. The best alternative is to “Hide it and deal with it in another property”.
So, here is the modified container class:
ApplicationInterfaceContainer.cs
using System; using System.Xml.Serialization; using System.Text; using System.Xml; using System.IO; using System.Xml.Linq; using System.Xml.XPath; using System.Linq; namespace Concept { [Serializable] public class ApplicationInterfaceContainer { [XmlIgnore()] public IBaseLoanApplication Application { get; set; } [XmlElement("Application")] public string ApplicationSerialized { get { return SerializationHelper.XmlSerializeApplication(Application); } set { Application = SerializationHelper.XmlDeSerializeApplication(value); } } public string Comments { get; set; } } }
To make life easier for developers, I created a utility class to do the serialization and deserialization work so that other containers can share them.
SerializationHelper.cs:
using System; using System.Xml.Serialization; using System.Text; using System.Xml; using System.IO; using System.Xml.Linq; using System.Xml.XPath; using System.Linq; namespace Concept { public class SerializationHelper { public static string XmlSerializeApplication(IBaseLoanApplication Application) { string retVal = null; if (Application != null) { //Get the real type Type applicationType = Application.GetType(); StringBuilder xmlDataBuffer = new StringBuilder(); //Set encoding to UTF8 and turn off Xml Declaration XmlWriterSettings writerSettings = new XmlWriterSettings(); writerSettings.Encoding = Encoding.UTF8; writerSettings.OmitXmlDeclaration = true; //Set type FullName. We will need this in the deserialization process to make it dynamic. XmlSerializerNamespaces nsType = new XmlSerializerNamespaces(); nsType.Add("type", applicationType.FullName); XmlSerializer serializer = new XmlSerializer(applicationType); using (XmlWriter xmlWriter = XmlWriter.Create(xmlDataBuffer, writerSettings)) { serializer.Serialize(xmlWriter, Application, nsType); } retVal = xmlDataBuffer.ToString(); } return retVal; } public static IBaseLoanApplication XmlDeSerializeApplication(string value) { IBaseLoanApplication baseApplication = null; XmlSerializer serializer; Type objType = null; if (string.IsNullOrEmpty(value) == false) { XDocument xDoc = XDocument.Parse(value); XElement applicationElement = xDoc.Elements().FirstOrDefault(); if (applicationElement != null) { if (string.IsNullOrEmpty(applicationElement.ToString()) == false) { XAttribute xAttrType = applicationElement.Attributes().FirstOrDefault(attr => attr.Name.LocalName.ToLower() == "type"); if (xAttrType != null) { objType = Type.GetType(xAttrType.Value); } if (objType != null) { serializer = new XmlSerializer(objType); using (StringReader reader = new StringReader(applicationElement.ToString())) { baseApplication = (IBaseLoanApplication)serializer.Deserialize(reader); } } } } } return baseApplication; } public static string GetXmlWithXmlSerializer(object obj) { StringBuilder xmlDataBuffer = new StringBuilder(); XmlSerializer serializer = new XmlSerializer(obj.GetType()); XmlWriterSettings writerSettings = new XmlWriterSettings(); writerSettings.Encoding = Encoding.UTF8; writerSettings.OmitXmlDeclaration = true; using (XmlWriter xmlWriter = XmlWriter.Create(xmlDataBuffer, writerSettings)) { serializer.Serialize(xmlWriter, obj); } return xmlDataBuffer.ToString(); } } }
Let’s see if we can serialize the container.
Program.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Serialization; using System.Xml; using System.Runtime.Serialization; using System.IO; namespace Concept { class Program { static void Main(string[] args) { //Create a new HomeLoanApplication IHomeLoanApplication homeLoanApplication = new HomeLoanApplication(); homeLoanApplication.FirstName = "Prodip"; homeLoanApplication.LastName = "Saha"; homeLoanApplication.AppId = 100; homeLoanApplication.LoanAmount = 400000; homeLoanApplication.HomePrice = 500000; //ApplicationInterfaceContainer with IBaseLoanApplication Application property. ApplicationInterfaceContainer applicationContainer = new ApplicationInterfaceContainer(); applicationContainer.Application = homeLoanApplication; applicationContainer.Comments = "I am a Home Loan Applicant."; //XML Serialize IBaseLoanApplication Application property on ApplicationInterfaceContainer string myResult =SerializationHelper.GetXmlWithXmlSerializer(applicationContainer); Console.WriteLine(myResult); //Deserialize the ApplicationInterfaceContainer ApplicationInterfaceContainer deserializedApplicationInterfaceContainer = DesWithXmlSerializer(myResult, applicationContainer); //Create a new CarLoanApplication ICarLoanApplication carLoanApplication = new CarLoanApplication(); carLoanApplication.FirstName = "John"; carLoanApplication.LastName = "Doe"; carLoanApplication.MiddleName = "Smith"; carLoanApplication.AppId = 200; carLoanApplication.LoanAmount = 19000; carLoanApplication.CarPrice = 25000; //ApplicationInterfaceContainer with IBaseLoanApplication Application property but as CarLoanApplication //See the benefit of using interface here. No casting is required! applicationContainer.Application = carLoanApplication; applicationContainer.Comments = "I am a Car Loan Applicant."; //XML Serialize IBaseLoanApplication Application property on ApplicationInterfaceContainer myResult = SerializationHelper.GetXmlWithXmlSerializer(applicationContainer); Console.WriteLine(myResult); //Deserialize the ApplicationInterfaceContainer deserializedApplicationInterfaceContainer = DesWithXmlSerializer(myResult, applicationContainer); //ApplicationBaseClassContainer with BaseLoanApplication property. ApplicationBaseClassContainer applicationBaseClassContainer = new ApplicationBaseClassContainer(); applicationBaseClassContainer.Application = homeLoanApplication as BaseLoanApplication; applicationBaseClassContainer.Comments = "I am ApplicationBaseClassContainer with HomeLoanApplication."; myResult = SerializationHelper.GetXmlWithXmlSerializer(applicationBaseClassContainer); Console.WriteLine(myResult); applicationBaseClassContainer.Application = carLoanApplication as BaseLoanApplication; applicationBaseClassContainer.Comments = "I am ApplicationBaseClassContainer with CarLoanApplication."; myResult = SerializationHelper.GetXmlWithXmlSerializer(applicationBaseClassContainer); Console.WriteLine(myResult); //Happy Inheritence, Polymorphism and Serialization programming!!! Console.ReadLine(); } public static ApplicationInterfaceContainer DesWithXmlSerializer(string stringXml, object obj) { try { XmlSerializer serializer = new XmlSerializer(obj.GetType()); using (StringReader reader = new StringReader(stringXml)) { var desObj=serializer.Deserialize(reader); return desObj as ApplicationInterfaceContainer; } } catch (Exception ex) { Console.WriteLine(ex.ToString()); throw; } } public static string GetXmlWithDataContactSerializer(object obj) { StringBuilder xmlDataBuffer = new StringBuilder(); try { DataContractSerializer serializer = null; if (obj != null) { serializer = new DataContractSerializer(obj.GetType()); using (XmlWriter xmlWriter = XmlWriter.Create(xmlDataBuffer)) { serializer.WriteObject(xmlWriter, obj); } } return xmlDataBuffer.ToString(); } catch (Exception ex) { Console.WriteLine(ex.ToString()); throw; } } private static string GetXmlWithXmlObjectSerializer(XmlObjectSerializer serializer, object obj) { StringBuilder xmlDataBuffer = new StringBuilder(); XmlWriterSettings writerSettings = new XmlWriterSettings(); writerSettings.Encoding = Encoding.UTF8; using (XmlWriter xmlWriter = XmlWriter.Create(xmlDataBuffer, writerSettings)) { serializer.WriteObject(xmlWriter, obj); } return xmlDataBuffer.ToString(); } } }
You can download the complete project here.
XML produced by XmlSerializer looks much nicer than the xml rendered by DataContractSerializer but there are issues with xml serialization. When you have a public property on the container object and the property is a complex type, you experience serialization error if you don’t pay attention to the namespaces. You would have to decorate the container and the other types with same namespace (i.e. XmlRoot (namespace=”yournamespace”)).