Thursday, January 20, 2011

[ Dot Net Remoting ] - Serialization Explained


1. Introduction


In this article, we will explore the importance of the Serialisation and its attributes. For basic example on how to create dot net remoting read the previous article on dot net remoting. Here, we will explore the serialisation by creating two-console application one for the server and the other one is for the client. OK. Let us start.

2. IPurchase Interface for Server


First, start a console application and then provide a reference to the System.Runtime.Remoting reference to your application. Once this is done, right-click the project and select the Add|New Item from the context menu. Select the interface Icon, name the file as IPurchase.cs and click ok.



Add the following piece of code:

//Server_001: Interface on the Server
public interface IPurchase
{
    PurchaseInfo GetPurchaseInfo();
}

Here, the interface has a function called GetPurchaseInfo, which return an object PurchaseInfo to the caller. The purchase that we will implement is a serializable object. Also, we will set some attributes to it, which we will see later when we implement it. The remote object will implement this interface. So we have three parts. An interface, implemented by the remote object returns the serialised object to the remote client.

3. The PurchaseRem remote class for Server


Now add a class PurchaseRem to your Server project. This class is derived from MarshalByRefObject as well as the previously created interface. The implemented interface function just creates an object of PurchaseInfo and returns it back to the remote client. Below is the code for it:

//Server_002: Remote object on the Server side.
public class PurchaseRem : MarshalByRefObject , IPurchase 
{
    //Server_003: Constructor
    public PurchaseRem()
    {
        Console.WriteLine("Remote Object Created");
    }
    //Server_004: Implement the IPurchase Contract.
    public PurchaseInfo GetPurchaseInfo()
    {
        return new PurchaseInfo();
    }
}

4. Hosting the remote object on Server


I will cover implementing the PurchaseInfo class in the next section. Before that, we will go ahead and host our remote object on the server. This portion also already explained in the previous article and I do not want to repeat everything here once again.

Below is the code for required namespace inclusion:

//Server_005: Remoting Namespaces required for the Server
using System.Runtime;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;

The code below registers the remote object and waits for servicing the client:

//Server_006: Allocate the TCP channel 12000
TcpServerChannel channel = new TcpServerChannel(12000);
ChannelServices.RegisterChannel(channel, false);

//Server_007: Register the class that implements IPurchase
RemotingConfiguration.RegisterWellKnownServiceType(typeof(PurchaseRem), "IPurchase", WellKnownObjectMode.SingleCall);

//Server_008: Break the Server from Exit
System.Console.WriteLine("Press Any Key to Halt the server");
System.Console.ReadLine();

Note that at tag 007: We registered the Remote object with the name of the interface.

5. Implementation PurchaseInfo class on server


1) Add a class called PurchaseInfo to your project. Then include the namespace required for the serialisation. As you know, this is the class we are going to implement serialisation and its attributes. Below is the code:

//Server 017: Namespace for Serialization
using System.Runtime.Serialization;

2) Mark the class serializable by defining the first attribute Serializable. Below is code change:
//Server 009: Make Purchaseinfo class serializable
[Serializable()]
public class PurchaseInfo

3) Next set of private variables is declared. To avoid database, I kept these variables (Too many you may think) so that the information is retrieved from the server based on the given id. Below is the declaration:

//Server 010: Private Variables
private string Customer_id1;
private string Customer_Name1;
private string Purchase_Made1;
private string Customer_country1;

private string Customer_id2;
private string Customer_Name2;
private string Purchase_Made2;
private string Customer_country2;

4) The constructor sets hard coded values for these variables. The aim is to explore the serialisation technique and so do not worry about the coding standard. Below is the code:

//Server 011: Constructor to hard code variables.
public PurchaseInfo()
{
    Customer_id1 = "100";
    Customer_Name1 = "Peter John";
    Purchase_Made1 = "120";
    Customer_country1 = "USA";
    Customer_id2 = "101";
    Customer_Name2 = "Susie Marie";
    Purchase_Made2 = "110";
    Customer_country2 = "Englang";

    Console.WriteLine("Purchase Info Initialized...");
}

5) The function GetPrchaseInfo takes customer id as a string and then compares the id with the hard coded value (in the previous step) to retrieve the required information. The information is returned as a string. Below is the code for this function:

//Server 012: Get the Purchase information for the customer based on the customer id supplied
public string GetPurchaseInfo(string CustId)
{
    string return_str;
    if (CustId == Customer_id1)
    {
        return_str = Customer_Name1 + " Purchased " + Purchase_Made1 + " Books from " + Customer_country1;
    }
    else if (CustId == Customer_id2)
    {
        return_str = Customer_Name2 + " Purchased " + Purchase_Made2 + " Books from " + Customer_country2;
    }
    else
    {
        return_str = "Customer with that id does not exists";
    }
    Console.WriteLine("Purchase Info Retrieved for {0}", CustId);
    return return_str;
}

By looking the above code you may say the Id is passed from the client to server and the server returns the information that corresponds to the supplied id.  If you say that then you are wrong. Note that the class is not a remote class. It is a serializable class. From the previous two statements, the class object is sent as a stream to the client. The process of converting the object into bit stream is called Serializing. The process of converting the bit stream to a meaningful object back is de-serializing.

If you remember the GetPurchaseInfo method (Don’t remember J. OK. Look at section 3) of our remote object returns the class that we are implementing now. This function call will perform serialisation at the server and de-serialization at the client end. So the client gets the object back and the call to GetPurchaseInfo (Serializable class. I kept the same function name. ) method of PurchaseInfo class is going to happen in the object in the client itself. Hope, you got the justification that passed in customer id and the return value does not need to be transmitted between server and client. This is explained in the below picture:



6) There are four attributes, which provides you with the way to make changes on the data member of the serialised class. These attributes are: [OnDeserializing()],[OnDeserialized()],[OnSerializing()],[OnSerialized()]

The attribute names are self-explained. For example, OnSerializing means when serialising is going on.. that is, when packing the class object into bit streams. OnSerialized means, after the serialisation (Packing into bit stream), is completed. These attributes can be applied to a function so that the function acts as an event handler. If you mark the function say abc() with an attribute OnSerialized, the function will get called after the sterilisation of the class is completed.

Below is the four functions applied with the above said four serialising attributes.

//Server 013: This routine is called when server is on the way of deserializing [Unpacking]
[OnDeserializing()]
public void OnDeserializing(StreamingContext cont)
{
    Console.WriteLine("Un-Packing the data to process");
}

//Server 014: This routine is called when the Server finished Unpacking.
[OnDeserialized()]
public void OnDeserialized(StreamingContext cont)
{
    Console.WriteLine("Data un-packed");
}

//Server 015: This routine is called when the Server is doing the Serialisation [Packing]
[OnSerializing()]
public void OnSerializing(StreamingContext cont)
{
    Console.WriteLine("Packing the data to deliver");
}

//Server 016: Routine called when the server finished packing the data.
[OnSerialized()]
public void OnSerialized(StreamingContext cont)
{
    Console.WriteLine("Data packed");
}

6. Preparing the Client Application


Now let us start the client application. Add new Visual C# Console application to our existing server console project. Then add the server project as a reference. If you are a beginner, you can see the steps for this in my previous article on remoting. Once the project is added place the following code on the application startup. Again, this is the same step discussed in the previous article. Read it Here

//Client_002: Get hold of remote object reference through interface
ChannelServices.RegisterChannel(new TcpClientChannel(), false);
IPurchase purchase = (IPurchase)(Activator.GetObject(typeof(IPurchase), "tcp://localhost:12000/IPurchase"));

//Client_003: Get the Serialized Object.
PurchaseInfo pinfo = purchase.GetPurchaseInfo();
Console.WriteLine(pinfo.GetPurchaseInfo("101"));
Console.WriteLine("Press Any key to close the client..");
Console.ReadKey();

Note the usage of the interface IPurchase. Remember that the remote object also implements the IPurchase interface. Once we have the interface for the remote object, we use that to get the serialised object PurchaseInfo using the call to the interface function GetPurchaseInfo.
 Let us explore the function calls purchase.GetPurchaseInfo(); and pinfo.GetPurchaseInfo("101").

The interface reference purchase is a proxy on the client for the actual object created and resides on the server. The call through the proxy interface reference, ( Call GetPurchaseInfo) triggers the remote object to create an instance of PurchaseInfo. And this PurchaseInfo will be sent to the Client. Note the object is serialised on the server, the bit stream is sent to the client, and the client deserializes the bit stream to get the actual object. In our case, we got the object referred by pinfo like that.  Then, the call to the function GetPurchaseInfo happens in the client itself as we have the object in the client.

In Summary, from the above client code:
1) The object PurchaseRem resides on the server
2) Based on the interface function, the server creates PurchaseInfo object, Packs it as a bit stream, sent it to the client.
3) Client unpacks the bit stream to get the object. So PurchaseInfo resides on client before making a call to GetPurchaseInfo(Id)
4) The serialisation process takes place on Server
5) The DeSerialization process takes place on the client machine.

7) Running the Application


1) From your build output, run the Server executable first
2) Then run the client application exe
3) You will see the following output



Note the output marked by numbers 1,2,3,4. All the Console.Writeline statement is kept in the PurchaseInfo serialised object. But 1 and 2 displayed in the server and 3,4 displayed at the statement. Watch those statements in the Server application and you will get clear knowledge on how serialisation is performed and the usage of the serialising attributes discussed in this article.

Note: The App is developed in VS2005 IDE. If you have the latest version of IDE, say OK to the conversion dialog. Read the Basic Article on Dot net remoting by following the link through in the sidebar labels.

Source Code : Download

No comments:

Post a Comment

Leave your comment(s) here.

Like this site? Tell it to your Firend :)