Thursday, August 11, 2011

Serialization in .Net – Part-5 (Custom Serialization with Example)

Custom Serialization 

You can customize the serialization process by implementing the ISerializable interface on an object. This is particularly useful in cases where the value of a member variable is invalid after deserialization, but you need to provide the variable with a value in order to reconstruct the full state of the object.

Implementing ISerializable involves implementing the GetObjectData method and a special Constructor that will be used when the object is deserialized.

Custom serialization can be implement through the ISerializable interface. This interface implements following two:
  1. A method GetObjectData ( a part of the interface)

    GetObjectData is used to convert the object into the stream of bytes. 

    Without GetObjectData method, .NET would have no way of knowing how to convert your class into a bit stream, and it would throw an error.

  2.  
  3. A constructor will be used when the object is deserialized, that takes two parameters, an object of SerializationInfo, and StreamingContext.


     Serialization process


    Use OnSerializingAttribute and OnSerializedAttribute to mark methods that will be executed before and after the serialization takes place. 

    This method takes two arguments, one is the SerializationInfo object which implements the IFormatterConverter interface. We will use it in an example below. 


    The StreamingContext object contains information about the purpose of the serialized object. For example, a StreamingContext of the Remoting type is set when the serialized object graph is sent to a remote or unknown location.

    Deserialization process

    It is also possible to control how the deserialization process by implementing a constructor that takes a SerializationInfo and a StreamingContext as parameters.
    Use OnDeserializingAttribute and OnDeserializedAttribute to specify methods to run before and after the object is deserialized.


    The BinaryFormatter serialization object fires four events that the developer may implement: OnSerializing, OnSerialized, OnDeserializing and OnDeserialized.

    The events are implemented as attributes in the class implementing the ISerializable interface. The methods marked as serialization events must have a StreamingContext argument, or else a runtime exception occurs. The methods must also be marked as void

    If both interfaces are implemented, the OnDeserialization() method in the IDe-serializationCallback interface is called after the OnDeserialized event, as the example output below shows. 

    .Net provides the facility to intercept Serialization / DeSeralization by providing following events.
     
    Serialization:
    • OnSerializing

    • OnSerialized 

    DeSeralization
    • OnDesrializing

    • OnDeserialized

    • IDeserialazationCallback



    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    using System.Runtime.Serialization; // Namespace for Custom Serialization (ISerializable,SerializationInfo,StreamingContext)
    using System.Runtime.Serialization.Formatters.Binary;//Namespace for Binary Serialization
    using System.Xml.Serialization;//Namespace for XML Serialization
    using System.Runtime.Serialization.Formatters.Soap;//Namespace for Soap Serialization



    namespace CustomSerializationByManishAgrahari
    {
        [Serializable]//<-- This atribute is just required by BinaryFormatter and SoapFormatter
        public class Employee : ISerializable, IDeserializationCallback//<-- XMLSerializer needs that the class is defined as public 
        {
            public int id;
            public string name;//<-- public properties to serialize.
            //They  will be serialize by the three formatters. 
            private string address;//<--this private field won't be serialized using XmlSerialization.
            //They will be serialized using Binary or Soap formatters 

            [NonSerialized] //<-- Using this attribute, the field "gender" won't be
            //serialized (by any of the three serializers) 
            public string gender;
            public string fullAddress;

            //let's create a method to set the private properties. 
            public void SetAddress(string address)
            {
                this.address = address;
            }

            public string GetAddress()
            {
                return this.address;
            }

            public Employee()
            { }

            public Employee(SerializationInfo info, StreamingContext context)
            {
                // Deserialization Constructor

                //the deserialization process by implementing a constructor
                //that takes a SerializationInfo and a StreamingContext as parameters.

                Console.WriteLine("Deserializing constructor");
                id = Convert.ToInt32(info.GetValue("id", typeof(Int32)));

                name = Convert.ToString(info.GetValue("name", typeof(String)));

                address = Convert.ToString(info.GetValue("address", typeof(String)));

                gender = Convert.ToString(info.GetValue("gender", typeof(String)));

                fullAddress = Convert.ToString(info.GetValue("fullAddress", typeof(String)));

            }

            private void MakeFullAddress()
            {
                this.fullAddress = name + ", " + GetAddress() + ", India";
            }

            #region ISerializable Members

            [OnSerializing]
            private void OnSerializing(StreamingContext context)
            {
                //before serialization 
                Console.WriteLine("OnSerializing fired, before serialization");
            }

            [OnSerialized]
            private void OnSerialized(StreamingContext context)
            {
                //after serialization 
                Console.WriteLine("OnSerialized fired, after serialization");
            }

            [OnDeserializing]
            private void OnDeserializingDoAnyOperation(StreamingContext context)
            {
                //before deserialization 
                Console.WriteLine("OnDeserializing fired, before deserialization");
            }

            [OnDeserialized]
            private void OnDeserializedDoAnyOperation(StreamingContext context)
            {
                //after deserialization 
                Console.WriteLine("OnDeserialized fired, after deserialization");
            }

            public void GetObjectData(SerializationInfo info, StreamingContext context)
            {

                info.AddValue("id", id);

                info.AddValue("name", name);

                info.AddValue("address", address);

                info.AddValue("gender", gender);

                info.AddValue("fullAddress", gender);


            }

            void IDeserializationCallback.OnDeserialization(object sender)
            {
                Console.WriteLine("IDeserializationCallback.OnDeserialization method.");
                MakeFullAddress();
            }

            #endregion
        }

        class TestSerialization
        {
            public void BinarySerialize(string filename, Employee emp)
            {
                FileStream fileStreamObject = null;
                try
                {
                    fileStreamObject = new FileStream(filename, FileMode.Create);
                    BinaryFormatter binaryFormatter = new BinaryFormatter();
                    binaryFormatter.Serialize(fileStreamObject, emp);
                }
                finally
                {
                    fileStreamObject.Close();
                }
            }

            public object BinaryDeserialize(string filename)
            {
                FileStream fileStreamObject = null;
                try
                {
                    fileStreamObject = new FileStream(filename, FileMode.Open);
                    BinaryFormatter binaryFormatter = new BinaryFormatter();
                    return (binaryFormatter.Deserialize(fileStreamObject));
                }
                finally
                {
                    fileStreamObject.Close();
                }
            }


            public static void Main()
            {
                Console.WriteLine("*********  Custom Serialization *********");
                String file = @"C:\\Custom_Serialization.txt";
                Employee objEmp = new Employee();
                objEmp.id = 1001;
                objEmp.name = "Manish Agrahari";
                objEmp.SetAddress("Noida");
                objEmp.fullAddress = objEmp.name + ", " + objEmp.GetAddress() + ", India";

                // Serializataion
                TestSerialization objTestSerialization = new TestSerialization();
                objTestSerialization.BinarySerialize(file, objEmp);
                Console.WriteLine("Object Saved on Location: " + file);

                // Deserialization of the object
                Employee objEmpDeserialize = (Employee)objTestSerialization.BinaryDeserialize(file);
                Console.WriteLine("*********  After Deserialization *********");
                Console.WriteLine("Employee ID: " + objEmpDeserialize.id);
                Console.WriteLine("Employee Name: " + objEmpDeserialize.name);
                Console.WriteLine("Employee Address: " + objEmpDeserialize.GetAddress());
                Console.WriteLine("Employee Gender: " + objEmpDeserialize.gender); //<-- Here Gender will be blank during deserialization
                //Because It could not be serialized due to [NonSerialized] Attrubute
                Console.WriteLine("Employee Full Address: " + objEmpDeserialize.fullAddress);
                Console.ReadLine();
            }

           


        }


    }





    In above program, The fullAddress field is not serialized, but recalculated when the Employee object is deserialized using IDeserializationCallback.OnDeserialization” funciton.
           
    The four events that are fired during (de)serialization illustrate the potential use of the events to the developer. The methods in this example write the progress of the (de)serialization process to the console.
     The OnDeserialization() method is called last, not between the OnDeserializing and OnDeserialized events.

    We can implement a SOAP formatter instead of a binary formatter by replacing the instance of BinaryFormatter with an instance of SoapFormatter. To instance the SoapFormatter, make sure that the System.Runtime.Serialization.Formatters.Soap namespace is referenced.

    The .NET Framework 3.5 documentation states that the SoapFormatter class is obsolete. However, developers will come across implementations of this class, especially in assemblies requiring portable object graphs.

     The SoapFormatter object also supports firing the four serialization events, and the console output is identical to the output when the BinaryFormatter object is implemented.

    The SoapFormatter creates a serialized object in a human readable format. The Serialization.txt file contains the following XML output:

     Custom serialization in .NET allows the developer complete control of the (de)serialization process. The sequence of events and the implemented interfaces and attributes should be understood before designing a serializable class. The serialization formatter choice will dictate class design. A design that incorporates serialization processes for a wide range of applications should be implemented if there are undecided issues of deployment and integration.


    Final review

    0 comments: