Introduction
.NET Remoting allows for a clean separation between client-side code and server-side code through the use of interfaces. This allows a developer to distribute generic interfaces to client machines and change the server-side code without having to redistribute the code changes back to the clients.
In this article, we will create a remote object and reference it only by the interface. We will also create an interface DLL that both the client and server code use.
Step 1. Creating The Interface Library
Launch Visual Studio.NET Beta 2, then choose File->New->Project. Choose to create a new "C# Library" and name it RemotingExampleInterfaces, then click on OK. The purpose of this library is to declare the object interfaces that we will use on both the client and server-side.
The code for this library is very simple, but a few notes need to be placed on the code below.
First, instead of declaring classes, we are declaring interfaces. This tells the compiler that we're not actually creating a class; we are creating a "template" for a class. An interface defines what Methods the object should have as well as the parameters and return values for these methods. Note the ';' at the end of each method declaration instead of the '{}'s that would normally contain the method's implementation. The final thing to mention on interfaces is that they have no access modifiers such as public or private. That is defined by the class that we choose to implement our interface in.
using System;
namespace RemotingExampleInterfaces
{
public interface IResume
{
String GetFormattedResume();
String GetFormattedName();
}
public interface IRemotingExampleService
{
IResume GetResume();
String GetFormattedResume();
}
}
Choose Build from the Build menu to compile this DLL. The output will be placed in the bin/Debug subdirectory of your project directory.
Step 2. Create The Implementation And Server Object
Now that we have defined the interfaces we plan on using, we need to create an implementation of these interfaces that does something. In a real project, you would create another shared library with your implementation to aid code reuse and keep your project modular. In this simple example, we'll just create a console application that provides an inline implementation of our interfaces.
Go to File->New->Project and choose C# Console application, then click on OK.
Since we are making use of the System.Runtime.Remoting the namespace, we will need to add a reference to the correct DLL. This is done by clicking Project->Add Reference, clicking on the ".NET" tab, and choosing System.Runtime.Remoting.DLL.
We also need to add a reference to the interfaces DLL we created in Step 1. This is done by choosing Project->Add Reference, clicking "Browse," and choosing the. DLL file in the bin/Debug directory of the RemotingExampleInterfaces project we created in Step 1.
public class Resume : MarshalByRefObject, IResume
{
// Class implementation for IResume interface
}
public class RemotingExampleService : MarshalByRefObject, IRemotingExampleService
{
// Class implementation for IRemotingExampleService interface
}
Take a look at these two class declarations. Each is a subclass of the MarshalByRefObject type. This is very important to the use of these objects in .NET remoting. Also, each of these objects is an implementation of the IResume and IRemotingExampleService interfaces we created in Step 1. Note that in these classes are methods that match the names of the methods we defined in our interface.
If you have questions about the TcpServerChannel or related topics, check out my article entitled, "Introduction to .NET Remoting" hosted on this site.
The complete code for the object is below.
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using RemotingExampleInterfaces;
using System.Runtime.Serialization;
namespace RemotingInterfaceServer
{
public class EntryPoint
{
public static void Main(string[] args)
{
TcpServerChannel channel = new TcpServerChannel(9988);
ChannelServices.RegisterChannel(channel);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(RemotingExampleService), "RemotingExampleService", WellKnownObjectMode.SingleCall);
Console.WriteLine("Press Any Key");
Console.ReadLine();
}
}
public class RemotingExampleService : MarshalByRefObject, IRemotingExampleService
{
public RemotingExampleService() { }
public IResume GetResume()
{
Resume resume = new Resume();
resume.FirstName = "David";
resume.LastName = "Talbot";
resume.Title = "Senior Software Architect";
resume.Body = "Did some cool stuff";
return (IResume)resume;
}
public string GetFormattedResume()
{
return GetResume().GetFormattedResume();
}
}
[Serializable]
public class Resume : MarshalByRefObject, IResume
{
public string FirstName, LastName, Body, Title;
public string GetFormattedResume()
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append("*" + GetFormattedName() + "*\n");
sb.Append("--" + Title + "--\n");
sb.Append("--------------------------\n");
sb.Append(Body);
return sb.ToString();
}
public string GetFormattedName()
{
return FirstName + " " + LastName;
}
}
}
Compile this program by going to Build->Build and note the location of the generated executable file found in the /bin/debug subdirectory of this project.
Step 3. Create The Client
To create the client program, go to File->New->Project and choose a C# Console Application named RemotingExampleClient.
IRemotingExampleService resService = (IRemotingExampleService)Activator.GetObject(typeof(IRemotingExampleService), "tcp://localhost:9988/RemotingExampleService");
Console.WriteLine("RESUME:\n" + resService.GetFormattedResume());
Notice that we're using the IRemotingExampleService here instead of RemotingExampleService? That is because our client-side code knows only about the interface, not the implementation the server is using. The result of this is the server is serving "RemotingExampleService", but our client is using "IRemotingExampleService".
IResume aResume = resService.GetResume();
Console.WriteLine("NAME: " + aResume.GetFormattedName());
Console.WriteLine("RESUME: " + aResume.GetFormattedResume());
This section of code makes use of the instance of IRemotingExampleService to return yet another interface. Here we don't have to make any calls to the Activator object because the .NET runtime accesses the server's Resume implementation transparently using the IResume interface.
To make this code work, we will need to add a reference to both our RemotingExampleInterfaces and System.Runtime.Remoting DLL as we did in step 2. Without this, you will get compiler errors.
The complete code listing is below. For more information on the Activator object, see my article titled "Introduction to .NET Remoting" on this site.
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels;
using RemotingExampleInterfaces;
namespace RemotingExampleClient
{
class RemotingExampleClient
{
static void Main(string[] args)
{
ChannelServices.RegisterChannel(new TcpClientChannel());
IRemotingExampleService resService = (IRemotingExampleService) Activator.GetObject(typeof(IRemotingExampleService), "tcp://localhost:9988/RemotingExampleService");
Console.WriteLine("RESUME:\n" + resService.GetFormattedResume());
IResume aResume = resService.GetResume();
Console.WriteLine("NAME: " + aResume.GetFormattedName());
Console.WriteLine("RESUME: " + aResume.GetFormattedResume());
Console.WriteLine("Press any key to continue...");
Console.Read();
} // END OF MAIN METHOD
} // END OF RemotingExampleClient class
} // END OF RemotingExampleClient namespace
Compile this project and note the location of the. EXE file.
Running the completed example
Run the server application we created in step 2 by double-clicking the .exe file we created. Then run the client application by double-clicking the .exe file created in step 3. If all goes well, you should see the formatted resume output.
Conclusion
Interfaces are the preferred way to access .NET remote objects. They enable the developer to create clean code with a complete separation between the client-side use of remote objects and the server-side implementation. In most cases, it is a good idea to use a DLL for your interfaces and another DLL for your implementation in order to maximize reuse potential.