DekGenius.com
[ Team LiB ] Previous Section Next Section

Recipe 3.33 Choosing a Serializer

Problem

The FCL contains several classes to allow objects to be serialized into different formats. Choosing the correct format for your task and remembering how to use that format can become a chore, especially when there is a mixture of different formats and all of them are on disk. You need some way of simplifying the serialization interfaces to make serialization easy without worrying about the underlying differences in the serialization classes. This will also allow other developers on your team to become proficient with the use of the various serializers more quickly.

Solution

Use the façade design pattern to create the following Serializer class:

using System;
using System.Collections;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization; 
using System.Runtime.Serialization.Formatters.Soap;

// Note that you must also add a reference to the following assembly:
//  System.Runtime.Serialization.Formatters.Soap.dll

[Serializable]
public class Serializer
{
    public Serializer( ) {}

    protected Hashtable serializationMap = new Hashtable( );
    protected Hashtable serializationTypeOfMap = new Hashtable( );

    // Serialize an object
    public void SerializeObj(object obj, string destination)
    {
        SerializeObj(obj, destination, SerializationAction.Default);
    }

    public void SerializeObj(
      object obj, string destination, SerializationAction action)
    {
        if (action == SerializationAction.RetainAssemblyInfo   ||
            action == SerializationAction.RetainPrivateMembers ||
            action == SerializationAction.SmallestFootprint    ||
            action == SerializationAction.Default)
        {
            BinarySerializeObj(obj, destination);
            serializationMap.Add(destination.ToUpper( ), DeserializationType.Binary);
        }
        else if (action == SerializationAction.MakePortable ||
                 action == SerializationAction.AsSOAPMsg)
        {
            SoapSerializeObj(obj, destination);
            serializationMap.Add(destination.ToUpper( ), DeserializationType.SOAP);
        }
        else  if (action == SerializationAction.AsXML ||
                  action == SerializationAction.SendToXMLWebService)
        {
            XmlSerializeObj(obj, destination);
            serializationMap.Add(destination.ToUpper( ), DeserializationType.XML);
            serializationTypeOfMap.Add(destination.ToUpper( ), 
              obj.GetType( ).FullName);
        }
    }

    private void BinarySerializeObj(object obj, string destination)
    {
        BinaryFormatter binFormatter = new BinaryFormatter( );
        Stream fileStream = new FileStream(destination, FileMode.Create, 
          FileAccess.Write, FileShare.None);
        binFormatter.Serialize(fileStream, obj);
        fileStream.Close( );
    }

    private void SoapSerializeObj(object obj, string destination)
    {
        SoapFormatter SOAPFormatter = new SoapFormatter( );
        Stream fileStream = new FileStream(destination, FileMode.Create, 
          FileAccess.Write, FileShare.None);
        SOAPFormatter.Serialize(fileStream, obj);
        fileStream.Close( );
}

    private void XmlSerializeObj(object obj, string destination)
    {
        XmlSerializer XMLFormatter = new XmlSerializer(obj.GetType( ));
        Stream fileStream = new FileStream(destination, FileMode.Create, 
          FileAccess.Write, FileShare.None);
        XMLFormatter.Serialize(fileStream, obj);
        fileStream.Close( );
    }

    // DeSerialize an object
    public object DeSerializeObj(string source)
    {
        return (DeSerializeObj(source, 
               (DeserializationType)serializationMap[source.ToUpper( )]));
    }

    public object DeSerializeObj(string source, DeserializationType type)
    {
        object retObj = null;

        if (type == DeserializationType.Binary)
        {
            retObj = BinaryDeSerializeObj(source);
            serializationMap.Remove(source.ToUpper( ));
        }
        else if (type == DeserializationType.SOAP)
        {
            retObj = SoapDeSerializeObj(source);
            serializationMap.Remove(source.ToUpper( ));
        }
        else if (type == DeserializationType.XML)
        {
            retObj = XmlDeSerializeObj(source);
            serializationMap.Remove(source.ToUpper( ));
            serializationTypeOfMap.Remove(source.ToUpper( ));
        }

        return (retObj);
    }

    private object BinaryDeSerializeObj(string source)
    {
        BinaryFormatter binFormatter = new BinaryFormatter( );
        Stream fileStream = new FileStream(source, FileMode.Open, FileAccess.Read, 
                                          FileShare.None);
        object DeserializedObj = binFormatter.Deserialize(fileStream);
        fileStream.Close( );

        return (DeserializedObj);
    }    

    private object SoapDeSerializeObj(string source)
    {
        SoapFormatter SOAPFormatter = new SoapFormatter( );
        Stream fileStream = new FileStream(source, FileMode.Open, FileAccess.Read, 
                                          FileShare.None);
        object DeserializedObj = SOAPFormatter.Deserialize(fileStream);
        fileStream.Close( );

        return (DeserializedObj);
    }

    private object XmlDeSerializeObj(string source)
    {
        XmlSerializer XMLFormatter = new 
            XmlSerializer(Type.GetType((string)serializationTypeOfMap
                                       [source.ToUpper( )]));
        Stream fileStream = new FileStream(source, FileMode.Open, 
          FileAccess.Read, FileShare.None);
        object DeserializedObj = XMLFormatter.Deserialize(fileStream);
        fileStream.Close( );

        return (DeserializedObj);
    }
}

public enum SerializationAction
{
    Default = 0,
    RetainAssemblyInfo,
    RetainPrivateMembers,
    MakePortable,
    SmallestFootprint,
    SendToXMLWebService,
    AsSOAPMsg,
    AsXML
}

public enum DeserializationType
{
    Binary = 0,
    SOAP,
    XML
}

Discussion

The façade design pattern uses a façade class to provide a simple interface to a group of underlying objects that do similar work. Any client that wants to use one of the underlying objects can go through the façade object. In effect, the façade pattern abstracts away the complexities and disparities between the underlying classes. This allows a uniform, and much easier to use, interface to be presented to the clients that wish to use any of these underlying objects.

The façade object can decide which underlying object will be used to perform the action requested, but it is not required to do so. The user could even pass in one or more arguments allowing the façade object to determine which underlying object to use. The nice thing about this pattern is that if the client decides that they need more flexibility than is provided by the façade object, they can choose to use the underlying objects and contend with their individual complexities. Also, if other serialization classes are created, they can easily be added to the façade object without breaking the existing code.

The class that acts as the façade in this recipe is the Serializer class. This class abstracts away the various interfaces to the various serializers that ship with the FCL, namely:

System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
System.Xml.Serialization.XmlSerializer
System.Runtime.Serialization.Formatters.Soap.SoapFormatter

In addition, this class provides an enumeration called SerializationAction, which can be passed to the SerializeObj method for the Serializer to choose the best type of serialization object to use to serialize the input data. The various values of the SerializationAction enumeration and their meanings are:


Default

Uses the default serialization object, which is BinaryFormatter.


RetainAssemblyInfo

Uses the BinaryFormatter. This value is used when the assembly information needs to be retained by the serialization process.


RetainPrivateMembers

Uses the BinaryFormatter. This value is used when private members need to be added to the serialization stream.


SmallestFootprint

Uses the BinaryFormatter. This value is used when the client wants the serialization data in the most compact form possible. As an added benefit, this serialization method is also the fastest.


MakePortable

Uses the SoapFormatter. This value is used when the serialization data needs to be in the most portable form (i.e., SOAP).


AsSOAPMsg

Uses the SoapFormatter. This value tells the façade object to explicitly use the SoapFormatter.


SendToXMLWebService

Uses the XmlSerializer. This value is used when the serialized object will be sent to an ASP.NET XML web service.


AsXML

Uses the XmlSerializer. This value tells the façade object to explicitly use the XmlSerializer.

The interface to the Serializer object contains two sets of overloaded methods: SerializeObj and DeSerializeObj. Both SerializeObj methods accept an object to be serialized in the obj parameter and a location to store the serialized object in the destination parameter. The second SerializeObj method also has a parameter that accepts a SerializationAction enumeration, which was previously discussed. The first SerializeObj method does not have this parameter and so defaults to using the SerializationAction.Default enumeration value.

You need to have permissions to open a FileStream directly from your code in order to use this recipe. This recipe cannot be used in a partial-trust environment where you are obliged to get your FileStreams either from IsolatedStorage or from a FileDialog.


Both DeSerializeObj methods accept a source string indicating where the serialized object is located. The second overloaded DeSerializeObj method also accepts a DeserializationType enumeration. This enumeration contains three values—Binary, SOAP, and XML—and is used to explicitly inform the underlying Deserialize methods of which serialization objects to use. If the first DeSerializeObj method is called, the values cached in the SerializeObj methods are used to deserialize the object without the client having to remember various small details about the serialization process used to initially serialize the object. If the SerializeObj methods are not used to serialize the object, one of the various DeserializationType enumeration values can be explicitly passed as an argument to inform the DeSerializeObj method which underlying deserialization method to call.

The serializationMap and serializationTypeOfMap Hashtables are used to cache various pieces of information during the serialization process. The SerializeObj methods use the serializationMap Hashtable to map the destination of the serialized object to the type of serialization process used. This allows the DeSerializeObj methods to use the source parameter to locate the pertinent information in the serializationMap Hashtable. The serializationTypeOfMap is used only when the XmlSerializer object is used for serialization. Upon deserialization, the XmlSerializer uses the serializationTypeOfMap to locate the full type name that is to be deserialized.

The following code serializes an integer array to the file TestBinSerXML.txt and then deserializes it into the retArray variable:

Serializer s = new Serializer( );
s.SerializeObj(new int[10] {1,2,3,4,5,6,7,8,9,10}, @"C:\TestBinSerXML.txt", 
               SerializationAction.AsXML);
int[] retArray = (int[])s.DeSerializeObj(@"c:\TestBinSerXML.txt");

See Also

See the "Serializing Objects," "Introducing XML Serialization," and "Serialization Guidelines" topics in the MSDN documentation.

    [ Team LiB ] Previous Section Next Section