Introducing .NET Remoting


Abstract

.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 that will return an Object read in from the database. I've also included an alternate object that omits the database functionality in order to allow those that don't have a database available to still play with .NET remoting. Make sure you are using Visual Studio.NET Beta 2 when attempting this project.

Step 1: Creating The Shared Library

Click on File->New->Project. Choose to create a new "C# Library" and name it ResumeServerLibrary then click on OK. This will create the "shared vocabulary" that both our .NET Remote client and Server will use to communicate.

The full code is below, if you would like to skip the database access portions, replace the ResumeLoader object with:

public class ResumeLoader : System.MarshalByRefObject
{
public ResumeLoader()
{
System.Console.WriteLine("New Referance Added!");
}
public Resume GetResumeByUserID(decimal userID)
{
return new Resume(1);
}
}

The namespaces required for the object. Please remember, if you're getting errors that the System.Runtime.Remoting.Channels.Tcp namespace does not exist, make sure you added the referance to System.Runtime.Remoting.dll as described above.

using System;
using System.Runtime;
using System.Data.SqlClient;

The namespace we're using for this particular object is DotNetRemoteTest, the object below is a MarshalByRefObject which means that instead of passing ResumeLoader over the wire, we're creating a referance and all of the work including the database work happens completely on the server side.

namespace DotNetRemoteTest
{
public class ResumeLoader : System.MarshalByRefObject
{
private SqlConnection dbConnection;
public ResumeLoader()
{
this.dbConnection = new System.Data.SqlClient.SqlConnection();
this.dbConnection.ConnectionString ="data source=GRIMSAADO2K;initial catalog=underground;integrated security=SSPI;pers" +"ist security info=True;workstation id=GRIMSAADO2K;packet size=4096";
/*Your connection string will be different. Database connections are beyond the scope of this article
*If you do not know how to create a database connection, please use the alternate version of this object */
System.Console.WriteLine("New Referance Added!");
}
public Resume GetResumeByUserID(decimal userID)
{
Resume resume = new Resume();
try
{
dbConnection.Open();
SqlCommand cmd = new SqlCommand("SELECT ResumeID, UserID, Title, Body FROM Resume as theResume WHERE theResume.UserID="+ userID +"", dbConnection);
SqlDataReader aReader = cmd.ExecuteReader();
if(aReader.Read())
{
resume.ResumeID=aReader.GetDecimal(0);
resume.UserID=aReader.GetDecimal(1);
resume.Title=aReader.GetString(2);
resume.Body=aReader.GetString(3);
}
aReader.Close();
dbConnection.Close();
}
catch(Exception x) { resume.Title="Error:"+x; }
return resume;
}
}

The Resume object needs to be serializable in order to be a return type of a remotely referanced .NET Remote object. The reason for this is the object will have to be turned into raw data to be passed over the network, then re-assembled into an object again on the other end.

This object is extremely simple, and to add to the simplicity of this tutorial, the constructor even initializes the fields with some default content.

[Serializable]
public class Resume
{
private decimal resumeID, userID;
private String body, title;
public Resume(decimal resumeID)
{
this.ResumeID=resumeID;
this.UserID=1;
this.Body="This is the default body of the resume";
this.Title="This is the default Title";
}
public decimal ResumeID
{
get { return resumeID; }
set { this.resumeID=value; }
}
public decimal UserID
{
get { return userID; }
set { this.userID=value; }
}
public String Body
{
get { return body; }
set
{
this.body=value;
}
}
public String Title
{
get { return title; }
set
{ this.title=value; }
}
}
//END OF RESUME OBJECT
}
//END OF DotNetRemoteTest namespace

Compile this project and you should have a DLL avaiable now to be added to your other assemblies.

Step 2: Create The Server Object

There are actually several ways to create the server object, the most straightforward is described below. In Visual Studio.NET, Click on File->New Project. Choose a Command Line Application and name it ResumeSuperServer.

First things first, we need to add referances to the required DLLs that will make this program work. Go to Project->Add Referance, and add a referance to the DLL that we created in Step1 by clicking the "Browse" button.

In order to use the .NET remote functionality, you must add a referance to the DLL using Project->Add Referance. Under the .NET tab, choose System.Runtime.Remoting.DLL and click on OK. Side note, to the authors of the .NET books out there that will remain nameless--Details like this are extremely important to someone trying to compile a .NET Remoting "hello world" :).

Next you will need to add a referance to System.Runtime.Remoting.dll the same way we did in Step 1.
The object below is fairly simple and straight forward. Below I'll explain each of the 3 lines of code that really matter to .NET remoting.

The TcpServerChannel is one of the two types of channels supported by .NET remoting. 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 TCP/IP stack on the operating system.

TcpServerChannel channel = new TcpServerChannel(9932);
ChannelServices.RegisterChannel(channel);

The other type of channel that can be set up is HTTP and is done simply by using the HttpServerChannel object in the System.Runtime.Remoting.Channels.Http namespace. The differances between using an HTTP and a TCP channel can be summed up simply--If you are working with a local network connection it's best to use TCP because of it's enhanced performance over using HTTP. If you're working over the internet HTTP can sometimes be the only choice depending on firewall configurations. Keep in mind that if you do have control over the firewall, almost all firewalls can be configured to allow TCP traffic through on the port you've chosen to use for you object in addition to the DNAT you'll likely need to employ in most situations. If you don't know how to alter these rules, ask your system administrator.

RemotingConfiguration.RegisterWellKnownServiceType(typeof(ResumeLoader), "ResumeLoader", WellKnownObjectMode.SingleCall);

This line sets a few prameters on your service and binds the object you want to the name you want to use on this remote object. The first parameter is the object you're binding, typeof(ResumeLoader). 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 "tcp://localhost:9932/ResumeLoader". 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 while WellKnownObjectMode.Singleton uses one instance of the object for all callers.

The complete code for the object is below.

using System;
using System.Runtime;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Data.SqlClient;
using DotNetRemoteTest;
namespace ResumeServerServer
{
public class ResumeSuperServer
{
public static void Main(String[] args)
{
TcpServerChannel channel = new TcpServerChannel(9932);
ChannelServices.RegisterChannel(channel);
RemotingConfiguration.RegisterWellKnownServiceType(typeof(ResumeLoader),
"ResumeLoader", WellKnownObjectMode.SingleCall);
System.Console.WriteLine("Press Any Key");
System.Console.ReadLine();
}
}
}

Compile this program and note the location of the generated .EXE file.

Step 3: Create The Remote Client

The ResumeClinet object is our test user of our newly created ResumeSuperServer remote object. To start this project go to File->New->Project. Choose a Console Application as the application type and enter "ResumeClient" as the project's name. As in step 2, make sure you add a referance to our shared DLL created in step 1 and the System.Runtime.Remoting DLL.

The code below has two lines of particular interest to .NET remoting. The first line creates a TCP client channel. This channel is not bound to a port. The seond line actually gets a referance to our remote ResumeLoader object. The Activator.GetObject method returns a type of Object that we can then cast into our ResumeLoader. 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 TcpClientChannel());
ResumeLoader loader = (ResumeLoader)Activator.GetObject(typeof(ResumeLoader), "tcp://localhost:9932/ResumeLoader");

The complete code for our ResumeClient is below.

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using DotNetRemoteTest;
namespace ResumeClient
{
public class ResumeClient
{
public static void Main(string[] args)
{
ChannelServices.RegisterChannel(new TcpClientChannel());
ResumeLoader loader = (ResumeLoader)Activator.GetObject(typeof(ResumeServer), "tcp://localhost:9932/ResumeLoader");
if(rs==null)
{ Console.WriteLine("Unable to get remote referance"); }
else
{
Resume resume = loader.GetResumeByUserID(1);
Console.WriteLine("ResumeID:"+ resume.ResumeID);
Console.WriteLine("UserID:"+ resume.UserID);
Console.WriteLine("Title:"+ resume.Title);
Console.WriteLine("Body:"+ resume.Body);
}
Console.ReadLine();//Keep the window from closing before we can read the result.
}//END OF MAIN METHOD
}//END OF ResumeClient Object
}//END OF ResumeClientNamespace

Compile this project and note the location of the executable.

Testing It Out

Create a table in your database. with the following basic schema:

Table Name-Resume
ResumeID, numeric (autonumber)
UserID, numeric
Title, Char(30)
Body, Text

Double click on the Server exe that we created in step 2, then double click on the Client executable we created in step 3. If everything works out, you should see the row from the database that had the ResumeID of 1.

In closing, .NET remoting is very simple to use and can provide an excellent way to work with resources across your network or the internet.