.NET Remoting: The Interface 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 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 transparent to the client. What the client looks at is, are the methods and the interface that exposes these methods. Here is the complete source 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 has created a simple Windows Application.

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

We need to Add a 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 internetwork 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 TCP channel ( 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, type of ( (CUSTOMER_SERVER1 ). The second parameter is the String which 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.A 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 implement the methods that are exposed to the interface.

Note. The following declaration 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;
        }
    }
    public class Form1: System.Windows.Forms.Form {
        public System.Windows.Forms.TextBox textBox1;
        private System.ComponentModel.Container components = null;
        public Form1() {
            InitializeComponent();
        }
        protected override void Dispose(bool disposing) {
            if (disposing) {
                if (components != null) {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }
        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);
        }
        [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 the 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 returns rows from the database as a single string separated by a comma.

Note that we need to add a reference to our shared Interface_CustomerInfo.DLL created in Part I. Also, add a 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 second 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 an 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 transparent 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 statement ( against the NorthWind ) database.

NET remoting makes life very easy for us.

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