Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Custom Serialization using the SOAP Formatter - Advanced

0.00/5 (No votes)
19 Aug 2007 1  
A tutorial on custom serialization using the SOAP formatter - Part II

Introduction

Serialization has always been a tricky topic for programmers. Using C++ and MFC, I managed to develop a method of working that minimized the amount of effort needed. When I switched to C# and .NET programming, I noticed things got much worse instead of better. Microsoft promotes using automatic serialization, but if you want to do more fancy things like using versioning, you cannot use this technology. Custom serialization is a reasonable alternative. It took me quite some time to understand custom serialization with enough detail to be useful for my purpose. In the first part of this article, I covered the basics. In this part, I cover advanced topics, like serializing arrays, derived objects and versioning.

Using the Code

I provide one single solution space, with two sample projects. Serialization1 demonstrates the basics for custom serialization. The project Serialization2 is a more extensive example. For Serialization1, the code is explained in the previous part of this article. Serialization2 will be covered in this article. Both projects use a single file named program.cs for all classes. I used a simple console application to keep things as simple as possible.

Both articles are included in the demo project.

Unlike the first article, I will not explain the code sequentially. I will cover four topics, which are mixed through the code sample:

  • Using versioning
  • Serialization and inheritance
  • Serialization of an array of objects
  • Serialization mixed types objects

Using Versioning

If you write an application and you want to add new functions later, you should make sure the new version is compatible with older versions. If you add a new object, during deserialization you should create a default value for this object when the application is stored using an older version.

In MFC, Microsoft supported a method called Versionable Schema to support versioning. This had lots of limitations, one of them that it does not support inheritance. I present a way of working for versioning that overcomes these limitations.

To each class, you should add a constant named Version (or what you like). This constant holds the actual version of the class. A derived class has its own Version constant. If you add new member variables to a class, you should increase the version number in this constant.

During serialization, you must serialize the class version number as well. The identifier must contain the class name in this case, because custom serialization does not really support inheritance. If you would use Version constants in the base class and in the derived class, the Soap Formatter will complain that it cannot serialize the same object twice.

Serialization goes like this:

[Serializable]
public class ObjectList:ISerializable
    {
    private const Int32 Version=1;
     ....

    public virtual void GetObjectData(SerializationInfo info,
                StreamingContext context)
        {
        info.AddValue("ObjectListVersion",Version);
        ....
        }
    }

During deserialization, in the deserialization constructor, you must first read the Version. Then, using a switch statement you can handle different versions of this object.

protected ObjectList(SerializationInfo info,StreamingContext context)
    {
    Int32 Version=info.GetInt32("ObjectListVersion");
    ... // other initialization tasks

    switch(Version)
        {
        case 1:
            {
            ...... // reading object for version 1
            break;
            }
        default:
            {
            //error handler goes here
            break;
            }
        }
    }

Initially it gives some extra work, but as you see it is not difficult. The point is you must do it from the very beginning.

Serialization and Inheritance

The SOAP formatter does not really understand inheritance, it merely flattens your object structure. This is a problem, if you use a variable in a derived object with the same name it has in the base object. This is completely legal (the Version constant discussed before is a perfect example). So you must take care to provide enough context manually during serialization. If you forget, an exception will be raised.

During serialization, you must serialize the base object like this:

public override void GetObjectData(SerializationInfo info,
            StreamingContext context)
    {
    base.GetObjectData(info,context);
    info.AddValue("DerivedVersion",version);
    ....
    }   

All you need to do is make sure serialization identifiers are unique and you should call the method GetObjectData for the base object. Do not forget to set the override modifier!

Deserialization is even more simple. In the special deserialization constructor, you just invoke the constructor for the base class first:

protected DerivedObject(SerializationInfo info,StreamingContext context):
            base(info,context)
    {
    // deserialize derived class
    Int32 StoredVersion;
    StoredVersion=info.GetInt32("DerivedVersion");
    switch(StoredVersion)
        {
        case 1:
            {
            .....
            break;
            }
        default:
            {
            // error handler
            break;
            }
        }
    }

Serializing an Array of Objects

The SOAP formatter does not support serialization of generic types. You will have to do this manually. A problem is that you must provide a unique identification for each object.

I will show how to serialize a generic list of the type List<MyObject>. The trick has two parts. You must tell the serializer how many objects you want to serialize and you must give each object a unique name. So, we obtain the number of objects to store and serialize this number. When retrieving, we first retrieve the number of objects and then the objects. For naming the objects, you can use a string and append a sequence number to it.

This is the result:

public virtual void GetObjectData(SerializationInfo info,
                StreamingContext context)
    {
    info.AddValue("ObjectListVersion",Version);
    Int32 i=0;
    Int32 ListLength=theList.Count;
    info.AddValue("ListLength",ListLength);
    for(i=0;i<ListLength;i++)
        {
        info.AddValue("Object"+i.ToString(),theList[i]);
        }
    }       

Deserialization goes like this:

protected ObjectList(SerializationInfo info,StreamingContext context)
    {
    Type t_MyObject=Type.GetType("Serialization.MyObject");
    Int32 Listlength=0;
    Int32 Version=info.GetInt32("ObjectListVersion");
    theList=new List<myobject __designer:dtid="562949953421364" />();
    String str; // temp string
    switch(Version)
        {
        case 1:
            {
            Listlength=info.GetInt32("ListLength");
            for(Int32 i=0;i<Listlength;i++)
                {
                MyObject object1=(MyObject)
            info.GetValue("Object"+i.ToString(),t_MyObject);
                theList.Add(object1);
                break;
                }
            break;
            }
        default:
            {
            //error handler goes here
            break;
            }
        }
    }       

Note that you should add the object to the ObjectList explicitly.

Serializing Mixed Object Types

By now, you may have guessed that you can serialize mixed object types in an array. You should serialize the object type. While deserializing, you must deserialize the object type and construct a switch statement to deserialize the correct type.

In the code below, this is demonstrated:

public virtual void GetObjectData(SerializationInfo info,
                StreamingContext context)
    {

    ...

    for(i=0;i<ListLength;i++)
        {
        Type t=theList[i].GetType(); // get object type
        switch(t.ToString())
            {
            case "Serialization.MyObject":
                {
                String str=t.ToString()+i.ToString();
                info.AddValue("Type"+i.ToString(),t.ToString());
                info.AddValue("Object"+i.ToString(),theList[i]);
                break;
                }
            case "Serialization.DerivedObject":
                {
                String str=t.ToString()+i.ToString();
                info.AddValue("Type"+i.ToString(),t.ToString());
                info.AddValue("Object"+i.ToString(),theList[i]);
                break;
                }
            default:
                {
                System.Console.WriteLine("Serialization error in ObjectList,
                type "+t.ToString()+" unknown");
                break;
                }
            }
        }
    }

Deserialization

protected ObjectList(SerializationInfo info,StreamingContext context)
    {
    Type t_MyObject=Type.GetType("Serialization.MyObject");
    Type t_DerivedObject=Type.GetType("Serialization.DerivedObject");
    Int32 Listlength=0;
    Int32 Version=info.GetInt32("ObjectListVersion");
    theList=new List<MyObject>();
    String str; // temp string
    switch(Version)
        {
        case 1:
            {
            TestString=(String)info.GetString("Test");
            Listlength=info.GetInt32("ListLength");
            for(Int32 i=0;i$lt;Listlength;i++)
                {
                // Read type
                str=info.GetString("Type"+i.ToString());//read object type
                switch(str)
                    {
                    case "Serialization.MyObject":
                        {
                        MyObject object1=(MyObject)
                info.GetValue("Object"+i.ToString(),t_MyObject);
                        theList.Add(object1);
                        break;
                        }
                    case "Serialization.DerivedObject":
                        {
                        DerivedObject object2=
                (DerivedObject)
                info.GetValue("Object"+i.ToString(),
                t_DerivedObject);
                        theList.Add(object2);
                        break;
                        }
                    default:
                        {
                        // error handling, type not known
                        break;
                        }
                    }
                }
                break;
            }
        default:
            {
            //error handler goes here
            break;
            }
        }
    }      

XML File

I show the essential part of the XML file here without further comments. You may notice a minor issue in this file....

<SOAP-ENV:Envelope xmlns:xsi=...>
<SOAP-ENV:Body>
    <a1:ObjectList id="ref-1" xmlns:a1=....>
        <ObjectListVersion>1</ObjectListVersion>
        <Test id="ref-3">Test string</Test>
        <ListLength>3</ListLength>
        <Type0 id="ref-4">Serialization.MyObject</Type0>
        <Object0 href="#ref-5"/>
        <Type1 id="ref-6">Serialization.DerivedObject</Type1>
        <Object1 href="#ref-7"/>
        <Type2 href="#ref-4"/>
        <Object2 href="#ref-8"/>
    </a1:ObjectList>
<a1:MyObject id="ref-5" xmlns:a1=....>
    <Version>1</Version>
    <Idx>1</Idx>
</a1:MyObject>
<a1:DerivedObject id="ref-7" xmlns:a1=....>
    <Version>1</Version>
    <Idx>2</Idx>
    <DerivedVersion>1</DerivedVersion>
    <Derived>5</Derived>
</a1:DerivedObject>
<a1:MyObject id="ref-8" xmlns:a1=....">
    <Version>1</Version>
    <Idx>3</Idx>
</a1:MyObject>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Conclusion

I believe these samples provide you with enough basic information to develop your own applications. I did not try to make foolproof code. The code provided is just meant to explain the principles. I leave it to you to add sufficient exception handling and error processing to create robust code.

I hope you will appreciate this contribution to The Code Project, which really is the very best site for code development.

History

  • 18th August, 2007: First release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here