.NET Remoting: The Simple Approach

.NET Remoting provides a powerful and high performance way of working with remote objects. Architecturally, .NET Remote objects are a perfect fit for accessing resources across the network without the overhead posed by SOAP based Web services. .NET Remoting is easier to use than Java's RMI, but definitely more difficult than creating a Web service. In this article, we will create a remote object that will return rows from a database table. For the sake of simplicity I have used the NorthWind database that is packed with the installation of the Microsoft SQL Server.

Developed using the Release version of Microsoft .NET Visual Studio

Part I : Creating the Marshal Object .. CustomerLoader

Click on File->New->Project. Choose to create a new "C# Library" and name it CustomerInfo then click on OK. This will create the "shared vocabulary" that both our .NET Remote client and Server will use to communicate. Any database activity happens on the server side. If you create a separate object or a class that gets returned from this , you need to declare it as SERIALIZABLE.

The namespace we're using for this particular object is CustomerInfo, the object that is of the type MarshalByRefObject. A marshaled object allows us to access objects across application domain boundaries in applications that support remoting.

MarshalByRefObject is the base class for objects that communicate across application domain boundaries by exchanging messages using a proxy. Objects that do not inherit from MarshalByRefObject are implicitly marshal by value.

MarshalByRefObject objects are accessed directly within the boundaries of the local application domain. The first time an application in a remote application domain accesses a MarshalByRefObject, a proxy is passed to the remote application. Subsequent calls on the proxy are marshaled back to the object residing in the local application domain.

Here is the complete source code listing.

using System;
using System.Text;
using System.IO ;
using System.Data.SqlClient;
namespace CustomerInfo
{
public class CustomerLoader : System.MarshalByRefObject
{
private SqlConnection myConnection = null ;
private SqlDataReader myReader ;
public CustomerLoader()
{
Console.WriteLine("New Customer Instance Created");
}
public void Init(string userid , string password)
{
try
{
string myConnectString = "user id="+userid+";password="+password+
";Database=Northwind;Server=SKER;Connect Timeout=30";
myConnection =
new SqlConnection(myConnectString);
myConnection.Open();
if ( myConnection == null )
{
Console.WriteLine("OPEN NULL VALUE =====================");
return ;
}
}
catch(Exception es)
{
Console.WriteLine("[Error WITH DB CONNECT...] " + es.Message);
}
}
public void ExecuteSelectCommand(string selCommand)
{
Console.WriteLine("EXECUTING .. " + selCommand);
SqlCommand myCommand =
new SqlCommand(selCommand);
if ( myConnection == null )
{
Console.WriteLine("NULL VALUE =====================");
return ;
}
myCommand.Connection = myConnection;
myCommand.ExecuteNonQuery();
myReader = myCommand.ExecuteReader();
}
public string GetRow()
{
if ( ! myReader.Read() )
{
myReader.Close();
return "" ;
}
int nCol = myReader.FieldCount ;
string outstr ="";
for ( int i=0; i < nCol ; i ++)
{
if ( myReader.IsDBNull(i) ) continue ;
outstr+= myReader.GetString(i);
}
return outstr;
}
}
}

Compile this project to generate the CustomerInfo.DLL to be added to the server and client assemblies.

Part II :Creating the Server

In Visual Studio.NET, Click on File->New Project. Choose a Command Line Application and name it CustServer. I have created a simple Windows Application.

You can also play around by creating a simple console application.

We need to Add Reference to the CustomerInfo.DLL that will make use of the Marshal Object. Click on the Project->Add Reference, and add a reference to the DLL that we created in Part I by clicking the "Browse" button. In order to use the .NET remote functionality, you must add a reference to the System.Runtime.Remoting.DLL using Project->Add Reference under the .NET tab.

hts = new HttpServerChannel(8228);
ChannelServices.RegisterChannel(hts);

Net Remoting supports 2 types of channels . For local applications or ntranetwork you can use the TcpChannel for better performance. The other type , the HttpChannel can be used for internet applications In this example I am using the HttpChannel ( just for the heck of it !! ) .If you're working over the internet HTTP can sometimes be the only choice depending on firewall configurations This will set up the port number we want our object to respond to requests on, and the ChannelServices.RegisterChannel will bind that port number to the stack on the operating system.

RemotingConfiguration.RegisterWellKnownServiceType(typeof(CustomerLoader) ,
"CustomerLoader" , WellKnownObjectMode.Singleton);

The first parameter is the object you're binding, typeof ( CustomerLoad). The second parameter is the String that is the name for the object on the TCP or HTTP channel. For example, remote clients would refer to the above object as "http://localhost:8228/CustomerLoad". The third parameter tells the container what should be done with the object when a request for the object comes in.

WellKnownObjectMode.Single call makes a new instance of the object for each client WellKnownObjectMode.Singleton uses one instance of the object for all callers.

It makes sense to use the Singleton option here since we have to maintain a unique database connection that can be used across clients and SQL calls.

The complete code for the object is below.

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Runtime ;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using CustomerInfo;
namespace CustServer
{
///
/// Summary description for Form1.
///
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.TextBox textBox1;
HttpServerChannel hts ;
///
/// Required designer variable.
///
private System.ComponentModel.Container components = null;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
}
///
/// Clean up any resources being used.
///
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
this.textBox1 = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.Dock = System.Windows.Forms.DockStyle.Fill;
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(292, 273);
this.textBox1.TabIndex = 0;
this.textBox1.Text = "";
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Controls.AddRange(new System.Windows.Forms.Control[] {
this.textBox1});
this.Name = "Form1";
this.Text = "Form1";
this.TopMost = true;
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
}
#endregion
///
/// The main entry point for the application.
///
[STAThread]
static void Main()
{
Application.Run(
new Form1());
}
private void Form1_Load(object sender, System.EventArgs e)
{
hts =
new HttpServerChannel(8228);
ChannelServices.RegisterChannel(hts);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(CustomerLoader) ,
"CustomerLoader" , WellKnownObjectMode.Singleton);
textBox1.Text = "HTTP Channel Registered ...\r\nWaiting on Clients Connect" ;
}
}
}

Compile the Server Object.

Part III :Creating the Client ( CustClient )

The CustClient object is our test object of our newly created CustServer remote object. Create a new project by clicking File->New->Project. I have created a simple windows client that connects to the remote object , executes an SQL command and retuns rows from the database as a single string separated by comma.

Note that we need to add a reference to our shared CustomerInfo.DLL created in Part I. Also add reference to the System.Runtime.Remoting DLL using the References->AddRefrences option in Solution explorer.

Now Notice the 2 important lines in the program. The first line creates a HTTPClient channel. This channel is not bound to a port. The seond line actually gets a reference to our remote CustomerLoad object. The Activator.GetObject method returns a type of Object that we can then cast into our CustomerLoad. The parameters we pass in are extremely similar to what we passed to the RemotingConfiguration object on the server project. The first parameter is the Type of the object, the second is the URI of our remote object.

ChannelServices.RegisterChannel( new HttpClientChannel());
custl = (CustomerLoader)Activator.GetObject(
typeof(CustomerLoader
) , http://localhost:8228/CustomerLoader);
if ( custl == null )
{
Console.WriteLine("HTTP SERVER OFFLINE ...PLEASE TRY LATER");
return ;
}

custl is an object of type CustomerLoader.

Here is the complete listing.

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Runtime ;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using CustomerInfo;
namespace CustomerClient
{
///
/// Summary description for Form1.
///
public class Form1 : System.Windows.Forms.Form
{
CustomerLoader custl ;
private System.Windows.Forms.TextBox textBox1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.TextBox textBox2;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
///
/// Required designer variable.
///
private System.ComponentModel.Container components = null;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
}
///
/// Clean up any resources being used.
///
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
this.textBox1 = new System.Windows.Forms.TextBox();
this.button1 = new System.Windows.Forms.Button();
this.textBox2 = new System.Windows.Forms.TextBox();
this.button2 = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// textBox1
//
this.textBox1.Font = new System.Drawing.Fon
t("Verdana", 8.25F, System.Drawing.FontStyle.Bold
, System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.textBox1.Location = new System.Drawing.Point(0, 32);
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.ScrollBars = System.Windows.Forms.ScrollBars.Both;
this.textBox1.Size = new System.Drawing.Size(736, 176);
this.textBox1.TabIndex = 0;
this.textBox1.Text = "";
//
// button1
//
this.button1.Location = new System.Drawing.Point(544, 216);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(80, 24);
this.button1.TabIndex = 1;
this.button1.Text = "Run SQL";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// textBox2
//
this.textBox2.Location = new System.Drawing.Point(0, 248);
this.textBox2.Name = "textBox2";
this.textBox2.Size = new System.Drawing.Size(736, 20);
this.textBox2.TabIndex = 2;
this.textBox2.Text = "";
//
// button2
//
this.button2.Enabled = false;
this.button2.Location = new System.Drawing.Point(632, 216);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(104, 24);
this.button2.TabIndex = 1;
this.button2.Text = "Get Next Row";
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// label1
//
this.label1.Location = new System.Drawing.Point(0, 232);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(192, 16);
this.label1.TabIndex = 3;
this.label1.Text = "Type Your SQL Command Here";
//
// label2
//
this.label2.Location = new System.Drawing.Point(0, 8);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(480, 16);
this.label2.TabIndex = 4;
this.label2.Text = "Data Returned From Remote Object (
Field Values are separated by commas ) ";
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(736, 269);
this.Controls.AddRange(new System.Windows.Forms.Control[] {
this.label2,
this.label1,
this.textBox2,
this.button1,
this.textBox1,
this.button2});
this.Name = "Form1";
this.Text = "CustClient";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
}
#endregion
///
/// The main entry point for the application.
///
[STAThread]
static void Main()
{
Application.Run(
new Form1());
}
private void Form1_Load(object sender, System.EventArgs e)
{
ChannelServices.RegisterChannel(
new HttpClientChannel());
custl = (CustomerLoader)Activator.GetObject(
typeof(CustomerLoader
) , http://localhost:8228/CustomerLoader);
if ( custl == null )
{
Console.WriteLine("HTTP SERVER OFFLINE ...PLEASE TRY LATER");
return ;
}
custl.Init("skulkarni" , "" ) ;
}
private void button2_Click(object sender, System.EventArgs e)
{
textBox1.Text = custl.GetRow() ;
if ( textBox1.Text == "" )
button2.Enabled =
false ;
}
private void button1_Click(object sender, System.EventArgs e)
{
button2.Enabled =
false ;
textBox1.Text = "" ;
if ( textBox2.Text == "" )
{
MessageBox.Show("Enter a SQL Command" , "Error");
return ;
}
bool ret = custl.ExecuteSelectCommand(textBox2.Text);
if ( ! ret )
{
textBox1.Text = "Error Executing SQL command or 0 rows returned" ;
return ;
}
button2.Enabled =
true ;
textBox1.Text = custl.GetRow() ;
}
}
}

NET remoting makes life very easy for us.

Its very simple to use and can provide an excellent way to work with resources across your network or the internet.


Similar Articles