| Printable Version
NET REMOTING .. The Interface Approach
By Shripad Kulkarni
| Source Code : NetRemoting |
|
.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 WebServices. .NET Remoting is easier to use than Java's
RMI, but definately more difficult than creating a WebService. In this
article, we will create a remote object, and access this object using
the Interface. The object returns 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
|
| For my previous article in NetRemoting.
Visit NetRemoting ( A Simple Approach ) |
| In this example we will create a remote
object and access it only by the interface. |
| Part
I : Creating the Interface Library .. Interface_CustomerInfo |
|
Click
on File->New->Project. Choose to create a new "C# Library" and name it
Interface_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. The important thing
to note here is that , we define an object ICustomerInfo of the type Interface.
public
interface ICustomerInfo
This
interface exposes 3 methods. The actual work is done by the server, But
this is transaparent to the client. What the client looks at is, are the
methods and the interface that exposes these methods.Here is the complete
souce listing ...
|
using System;
namespace Interface_CustomerInfo
{
///
/// Summary description for Class1.
///
public interface ICustomerInfo
{
void Init(string username , string password) ;
bool ExecuteSelectCommand(string selCommand);
string GetRow();
}
}
|
| Compile this project to generate the Interface_CustomerInfo.DLL.
|
| Part
II :Creating the Server ( CustomerServer ) |
|
In Visual Studio.NET,
Click on File->New Project have created a simple Windows Application.
You can also play
around by creating a simple console application.
We need to Add Reference
to the Interface_CustomerInfo.DLL
that will make use of the Interface. 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 intranetwork applications
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 TcpChannnel ( just for the heck of it !! ) .The previous
application i mentioned above uses an HttpChannel. 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(CUSTOMER_SERVER1) ,
"CUSTOMER_SERVER1" , WellKnownObjectMode.Singleton);
|
|
The first parameter
is the object you're binding, typeof ( (CUSTOMER_SERVER1
). 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/CUSTOMER_SERVER1".
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.
Important
: The server class will actually implement the methods that are exposed
from the interface.
Note
the following decleration of the Customer_Server1 class
public class CUSTOMER_SERVER1 : MarshalByRefObject
, ICustomerInfo.
This
class inherits from the MarshalByRefObject object and ICustomerInfo interface
that we have created in the interface DLL in step 1.
|
| The complete code for the Server object
is below. |
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Text;
using System.IO ;
using System.Data.SqlClient;
using Interface_CustomerInfo;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels ;
using System.Runtime.Remoting.Channels.Tcp;
namespace CustomerServer
{
public class CUSTOMER_SERVER1 : MarshalByRefObject , ICustomerInfo
{
private SqlConnection myConnection = null ;
private SqlDataReader myReader ;
public CUSTOMER_SERVER1() { }
public void Init(string userid, string password)
{
try
{
MessageBox.Show("COMES HERE");
string myConnectString = "user id="+userid+";password="+password+
";Database=Northwind;Server=SKYWALKER;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 bool ExecuteSelectCommand(string selCommand)
{
try
{
Console.WriteLine("EXECUTING .. " + selCommand);
SqlCommand myCommand = new SqlCommand(selCommand);
if ( myConnection == null )
{
Console.WriteLine("NULL VALUE =====================");
return false ;
}
myCommand.Connection = myConnection;
myCommand.ExecuteNonQuery();
myReader = myCommand.ExecuteReader();
return true ;
}
catch ( Exception e )
{
return false ;
}
}
public string GetRow()
{
if ( ! myReader.Read() )
{
myReader.Close();
return "" ;
}
int nCol = myReader.FieldCount ;
string outstr ="";
object [] values = new Object[nCol];
myReader.GetValues(values);
for ( int i=0; i < values.Length ; i ++)
{
string coldata = values[i].ToString();
coldata = coldata.TrimEnd();
outstr += coldata + "," ;
}
return outstr;
}
}
///
/// Summary description for Form1.
///
public class Form1 : System.Windows.Forms.Form
{
public System.Windows.Forms.TextBox textBox1;
///
/// 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.Font = new System.Drawing.Font("Microsoft Sans Serif", 14.25F
, System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((System.Byte)(0)));
this.textBox1.Multiline = true;
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(288, 85);
this.textBox1.TabIndex = 0;
this.textBox1.Text = "textBox1";
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(288, 85);
this.Controls.AddRange(new System.Windows.Forms.Control[] {
this.textBox1});
this.Name = "Form1";
this.Text = "Interface_Server";
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)
{
TcpServerChannel tsc = new TcpServerChannel(8228);
ChannelServices.RegisterChannel(tsc);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(CUSTOMER_SERVER1)
, "CUSTOMER_SERVER2" , WellKnownObjectMode.Singleton);
textBox1.Text = "SERVER RUNNING .." ;
}
}
}
|
| Compile the Server Object. |
| Part
III :Creating the Client ( CustomerClient ) |
|
The CustomerClient
object is our test object of our newly created CustomerServer 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 Interface_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 TcpClientChannel.
This channel is not bound to a port. The seond line actually gets a reference
to our server CUSTOMER_SERVER1
object. The Activator.GetObject method returns a type of Object that we
can then cast into our CUSTOMER_SERVER1.
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.
Notice that we are using a ICustomerInfo
object. The server is serving the CUSTOMER_SERVICE1 object but we are
using the ICustomerInfo object. When you are calling the Init method of
the interface , the implemented version of the method , handled by the
server is activated. This process is completely transaprent to the client
when using the ICustomerInfo interface.
|
ChannelServices.RegisterChannel( new TcpClientChannel());
custl = (ICustomerInfo)Activator.GetObject(typeof(ICustomerInfo)
, "tcp://localhost:8228/CUSTOMER_SERVER1");
if ( custl == null )
{
Console.WriteLine("TCP SERVER OFFLINE ...PLEASE TRY LATER");
return ;
}
custl.Init("skulkarni" , "" ) ;
|
| 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.Tcp;
using Interface_CustomerInfo;
namespace CustomerClient
{
///
/// Summary description for Form1.
///
public class Form1 : System.Windows.Forms.Form
{
ICustomerInfo 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.Font("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 TcpClientChannel());
custl = (ICustomerInfo)Activator.GetObject(typeof(ICustomerInfo)
, "tcp://localhost:8228/CUSTOMER_SERVER1");
if ( custl == null )
{
Console.WriteLine("TCP 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() ;
}
}
}
|
|
Run the Server , then Run the client. Type in a SQL statemtent ( against
the NorthWind ) database.
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.
|
|