Implementation of XML Serialization and Data Contract Serialization Utility in .NET

0
125

Readers of the Topic – Developers

Coverage Topic

  • Basic idea about de-serialization and serialization
  • Advantage and disadvantages of xml Serialization
  • Implementation of xml serialization
  • White Box testing of xml serialization
  • Advantage and disadvantages of data-contract-serialization
  • Problem Analysis
  • Implementation of sortable data-contract serialization
  • White-Box testing of data-contract serialization

Let’s Drill Down the Basic Concept

The above diagram wants to say,

  • Deserialization: Transforming XML string to object.
  • Serialization: Transforming object to XML string.

XmlSerializer

  • Only Public Properties can be transformed.
  • It can serialize collections which can be inherited from IEnumerable and ICollection.
  • You don’t need to specify serialization attributes for every property that needs to be transformed. If you want to ignore to translate any property, then you have to use [XmlIgnore] attribute.

Limitation:

  • It can serialize only property
  • Properties should have the set and get functionality. It can’t serialize private or read only properties, indexers and methods.

Class Diagram

Project Creation

The problem can be solved many different ways. Right now, I am mainly focusing only on Xml Serialization and Data-Contract Serialization.

Basic Concept

I have used the following references into the project for the XML and data-contract serialization:

  • System.Xml.Serialization
  • System.Runtime.Serialization

I have also used System.IO namespace. We need the classes which are given bellow:

  • MemoryStrean
  • StreamWriter
  • StreamReader

Memory Stream Class

  • It stores data in memory (RAM)
  • It is used for fast and temporary storage

StreamWriter and StreamReader

These both of the classes are used for reading and writing character based data.

Flush with a StreamWriter

Flush is used to move information buffer to destination.

Implementation of Xml Serializer

There is no doubt that these codes are okay. It can serialize the object to xml string. You can use these codes without any problems.

Now let’s see another implementation with using block.

GetType vs. typeof:

Look at the above image, I have used the using block and these are not the major change in this implementation. Look at the no.1 (marked with red), I have used tag.GetType() instead of typeof(T). It is better to use GetType because of the code safety.

In the first implementation of the image, when I am going to call serializer.Serialize(streamWriter, tag), sometimes XmlSerializer throws a runtime exception because of the typeof(T).

If you don’t get the error, because of using the typeof(T), then you can have different opinions. Anyway, I’m going to use GetType instead of typeof(T).

Benefits

  • Because of the using block, dispose methods are automatically called. You don’t need to call it manually.
  • Even if, I did not use StreamWriter.Flush (No.2 in the codes of the first implementation).

Implementation of XML DeSerializer

Look at the above image; I have changed very silly things. You can use either one of the implementation. So, you can use these deserialization method for transforming xml string to object.

White Box Testing of XML Serializer/De-Serializer

For the white-box testing, I am going to use Behavior Driven Development (BDD) for the naming convention of the test method.

Concept of BDD

1. Given I am a beginner to the BDD technique, and I never use this technique before

2. When I read this tutorial for BDD

3. Then I have started to like it and finally I learn it.

Note: I’m avoiding the details of the BDD to keep this simple.

XML DeSerializer Testing

I am writing a method to test the DeSerializer method.

Naming Convention of a Test Method

Given_Valid_XML_String_For_Person_When_DeSerializer_Is_Called_Then_It_Should_Return_Valid_Person_Object()

Say, I have an xml string which has the name and address of a person.

='1.0'='utf-16'
<Person>
    <PersonId>044373a4-0f17-4ec4-8e1f-ac6d1d7873e7</PersonId>
    <LastName>Rony</LastName>
    <FirstName>HR</FirstName> 
    <Address> 
        <AddressId>b90c16b4-418a-4eca-80fb-8679a60b418e</AddressId> 
        <City>City</City> 
        <State>State</State> 
        <ZipCode>1200</ZipCode> 
    </Address> 
</Person>

The full test method is given bellow and it should be passed.

[TestMethod]
public void Given_Valid_XML_String_For_Person_When_DeSerializer_Is_Called_Then_It_Should_Return_Valid_Person_Object()
{
        Person personObj;

        string xmlData =
    @"<?xml version='1.0' encoding='utf-16'?>
    <Person>
        <PersonId>044373a4-0f17-4ec4-8e1f-ac6d1d7873e7</PersonId>
        <LastName>Rony</LastName>
        <FirstName>HR</FirstName>
        <Address>
            <AddressId>b90c16b4-418a-4eca-80fb-8679a60b418e</AddressId>
            <City>City</City>
            <State>State</State>
            <ZipCode>1200</ZipCode>
        </Address>
    </Person>";

    string expectedLastNamet = "Rony";

        personObj = xmlSerializerUtility.DeSerializer<Person>(xmlData);

        Assert.AreEqual(personObj.LastName, expectedLastNamet);
}

XML Serializer Testing

The serialize method is called inside the test method and it should be passed.

[TestMethod]
public void Given_Valid_PersonObj_When_Serialize_Is_Called_Then_It_Should_Return_Valid_XML_Data()
{
        Address address = new Address
    {
        AddressId = Guid.NewGuid(),
        City = "City",
        State = "State",
        ZipCode = "1200"
    };

    Person person = new Person
    {
        PersonId = Guid.NewGuid(),
        FirstName = "HR",
        LastName = "Rony",
        Address = address
    };

        string actualXmlData = null;

        actualXmlData = xmlSerializerUtility.Serializer<person>(person);

        Assert.IsNotNull(actualXmlData);
}

</person>

The Problem: I’m implementing a micro service. Say, there is a client interface which will send data as xml format to the service. I have no control over the client interface. Somehow, I have figured out the elements/properties which I need for the model (entity) classes. After analyzing the xml as well as the entity classes, I have got the following issues:

  • Service will receive XML string which has un-sorted ordered elements.
  • Existing model class contains IDictionary or IList.

I am focusing only on XML and Data Contract De-Serializer/Serializer.

DataContractSerializer

  • Public Properties and private fields can be transformed. But you have to mention [DataMember] attribute.
  • It doesn’t support xml attributes
  • It supports IList, IDictionary (HashTable) interface.

Limitation

  • XML should have to be alphabetically sorted.

Performance: usually, it is 10% faster than XmlSerializer.

Implementation of DataContract Serializer

Look at the above codes, it is almost similar the previous implementation except DataContractSerializer Class.

Now say, I have xml string which has the name and education properties of an Employee.

<Employee> 
    <LastName>Rony</LastName> 
    <FirstName>HR</FirstName> 
    <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId> 
    <Educations> 
        <Education> 
            <EducationId>748a5d33-2cda-454f-ab23-63f12ecccd76</EducationId> 
            <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId> 
            <DegreeText>Bachelors in computer science</DegreeText> 
        </Education> 
        <Education> 
            <EducationId>9491fdbb-e1e8-4781-bb61-749e837a0b11</EducationId> 
            <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId> 
            <DegreeText>Masters in computer science</DegreeText> 
        </Education> 
    </Educations> 
</Employee>

Look at the tag of the elements (LastName, FirstName, EmployeeId) in the xml strings which are not alphabetically ordered.

Problem Analysis

Again, if you look at the model class of the employee, then you will see that, at runtime, it is alphabetically ordered.

So, if I run the test, then I will get the invalid data in the object.

Look at the above object, it just gets the value of “LastName”. Because in the employee model class, first it gets the LastName before the FastName. So, it takes the value of the LastName from the xml and after that it ignores values of the FirstName and others.

Solutions

  • Sort the XML elements.
  • Remove the empty tags too.

I have added two extension methods for sorting and removing empty elements of the XML string.

internal static class XmlSorter 
{ 
    internal static void Sort(this XElement xElement, bool sortAttributes = true) 
    { 
                if (xElement == null) throw new ArgumentNullException("XElement is null");

                if (sortAttributes) 
        { 
            List <XAttribute> sortedAttributes = xElement.Attributes().OrderBy(a => a.ToString(), new CaseSensitiveComparer()).ToList(); 
            sortedAttributes.ForEach(a => a.Remove()); 
            sortedAttributes.ForEach(a => xElement.Add(a)); 
        }

                List <XElement> sortedChildren = xElement.Elements().OrderBy(e => e.Name.ToString(), new CaseSensitiveComparer()).ToList(); 
        if (xElement.HasElements) 
        { 
            xElement.RemoveNodes(); 
            sortedChildren.ForEach(c => c.Sort(sortAttributes)); 
            sortedChildren.ForEach(c => xElement.Add(c)); 
        } 
    }

    internal static void RemoveEmptyElement(this XElement xElement) 
    { 
                if (xElement == null) throw new ArgumentNullException("XElement is null");

                xElement.Descendants().Where(e => string.IsNullOrEmpty(e.Value)).Remove(); 
    } 
}

internal class CaseSensitiveComparer : IComparer 
        <string> 
{ 
    public int Compare(string x, string y) 
    { 
        return string.Compare(x, y, StringComparison.Ordinal); 
    } 
}

Sortable DataContract Serializer

Look at the above codes, I have wrapped the previous implementation of the DataContractSerializerUtility class.  I have called RemoveEmptyElements extension method to remove the empty tags from the xml string.

After removing the empty tags, it sorts the tags of the xml strings by calling the sort extension method.

White Box Testing of DataContract Serializer/De-Serializer

Now if I run the following test, then it will be passed the test. Now these methods are ready to use.

Deserialization Testing

[TestMethod]

public void Given_Valid_XML_Data_For_Employee_When_DeSerializer_Is_Called_Then_It_Should_Return_Valid_Employee_Object()

{

    
    ISerialization sortabledataContractSerializer = new DataContractSortableSerializerUtility(dataContractSerializer);

    
    Employee employeeObj;

    string xmlData = @"     
 <Employee> 
        <LastName>Rony</LastName> 
        <FirstName>HR</FirstName> 
        <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId> 
        <Educations> 
            <Education> 
                <DegreeText>Bachelors in computer science</DegreeText> 
                <EducationId>748a5d33-2cda-454f-ab23-63f12ecccd76</EducationId>
                <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId>
            </Education> 
            <Education> 
                <DegreeText>Masters in computer science</DegreeText> 
                <EducationId>9491fdbb-e1e8-4781-bb61-749e837a0b11</EducationId>
                <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId>
            </Education> 
        </Educations> 
      </Employee>";

    int expectedCount = 2;

        employeeObj = sortabledataContractSerializer.DeSerializer<Employee>(xmlData);

        Assert.AreEqual(employeeObj.Educations.Count, expectedCount); 
}

The expected result from the test is given bellow –

Serialization Testing

[TestMethod]
public void Given_Valid_EmployeeObj_When_Serialize_Is_Called_Then_It_Should_Return_Valid_XML_Data()
{
        ISerialization sortabledataContractSerializer = new DataContractSortableSerializerUtility(dataContractSerializer);

        Employee employee = new Employee();
    employee.EmployeeId = Guid.NewGuid();
    employee.FirstName = "HR";
    employee.LastName = "Rony";
 
    List<Education> educationList = new List<Education>();
    Education education = new Education();
    education.EducationId = Guid.NewGuid();
    education.EmployeeId = employee.EmployeeId;
    education.DegreeText = "Bachelors in computer science";
    educationList.Add(education);
 
    Education educationObj2 = new Education();
    educationObj2.EducationId = Guid.NewGuid();
    educationObj2.EmployeeId = employee.EmployeeId;
    educationObj2.DegreeText = "Masters in computer science";
    educationList.Add(educationObj2);

    employee.Educations = educationList;

        string actualXmlData = null;

        actualXmlData = sortabledataContractSerializer.Serializer<Employee>(employee);

        Assert.IsNotNull(actualXmlData);
}

I tried to show you, how to find out the problem as well as how to fix it quickly. Find the source code and full project. It is attached.

LEAVE A REPLY