devxlogo

Writing Advanced .NET Remoting Clients

Writing Advanced .NET Remoting Clients

The .NET framework has a number of powerful Remoting features to make working with remote objects simple especially in the client side code. You can use factory patterns to enable powerful features in the client side code. However, the client side code uses nothing more than ordinary interfaces.

Why not a Web Service?
Remoting provides a number of advantages over Web services but we can start with the way you use them. Web services are the equivalent of static function libraries. If the client creates an instance of the Web service and modifies a member variable of the object with one method call, then attempts to read the value from another method call, the value will be the default value for that variable, not the new value. That’s because Web services don’t maintain session state without considerable hacking outside of the SOAP standard. Therefore, when you need the ability to use remote objects, rather than remote methods, Remoting is the way to go.

Namespaces And DLLs used for Remoting
The namespaces used for Remoting in this article are, System.Runtime.Remoting, System.Runtime.Remoting.Channels, and System.Runtime.Remoting.Channels.Tcp. To use these namespaces in your own code, add a reference to the System.Runtime.Remoting.dll. To add the reference, select Project?> Add Reference from the VS.NET menu and then select the System.Runtime.Remoting.dll item from the list for the .NET tab.

An interface in C# is a pattern for a class with methods defined but left empty. Basically an interface defines the methods, parameters, and return values implementations of your interface must have.

In a Remoting context, think of the interface as being an agreement reached between the client and the server; interfaces provide the glue that allows lets the client and server play together nicely. You can imagine the client as thinking, “If I give you an integer for the WhatIsTwoPlusTwo() method, I want a string back.” Meanwhile, the server is thinking, “If you give me an integer for the WhatIsTwoPlusTwo method, I will perform my logic on that integer and give you a string back.”

The interface becomes a kind of symbolic version of the object that actually resides on the server. Your client side remoting code uses only the interfaces and not the actual objects. When your application runs it uses the object instantiated on the remote server transparently, even though your client side code has knowledge only of the interface.

In this article, you’ll see an example implementation designed to evaluate whether a test taker would make a good corporate accountant for a large energy company.

The interface for the sample code is simple, and contains only two methods. The method WhatIsTwoPlusTwo()must accept an integer for the answer argument, and returns a string. The method SetName() which accepts a string and returns nothing. The SetName() method should probably set a variable containing the name of the person taking the test, but remember that the methods in this interface are not implemented yet. You’ll write the implementation in the next section.

   public interface IAccountantTest   {      string WhatIsTwoPlusTwo(int answer);      void SetName(string name);   }

Put all your interfaces in a separate DLL project. Both the client and the server will need their own interface copies. Listing 1 contains a complete listing of the interface code.

After defining the interface you can create an implementation. It’s best to create the implementation in a separate class library project to make it easy to use the classes in other projects.

   public class CorporateAccountantTest :       MarshalByRefObject, IAccountantTest   {      private string name;      public CorporateAccountantTest()      {         name="David";         }      public string WhatIsTwoPlusTwo(int answer)      {      if(answer==4) { return "That's an "+          "acceptable answer, " + name + "."; }      else      {         return "That's right " + name +         ", 2+2 is whatever you want it to be!";      }      public void SetName(string name)      {         this.name = name;      }   }

The CorporateAccountantTest class inherits from MarshalByRefObject and also implements the IAccountantTest interface. Your Remoting classes must be subclasses of MarshalByRefObject, otherwise you will get a mysterious “not serializable” error.

   public class CorporateAccountantExamFactory :       MarshalByRefObject, IAccountantExamFactory   {      public IAccountantTest CreateAccountTestInstance()      {         return new CorporateAccountantTest();}      }   }

The only purpose of the CorporateAccountantExamFactory class is to create a CorporateAccountantTest instance and return it to the client as an interface that the client can treat only as IAccountantTest. You use the factory pattern because you want the client side object to be able to create instances of the object on the server using only its knowledge of the interfaces. You can’t create an instance of an interface using the new keyword so the factory helps out by letting the client create new instances of the object using a simple method call.

You usually implement a factory pattern as a class containing only static methods that return instances of objects. Think of a factory as being like a physical factory, you ask for something to be built, and the factory builds it and gives it to you.

In addition, the pattern serves to keep the entire object referenced between calls, so it behaves like an object and not a function library. If you had created the object by creating a remote reference directly to a CorporateAccountantTest instance you would have to do quite a bit more work to get a reference to the remote object. Listing 2 contains a complete listing of the implementation.

For your client program to be able to use the remote object, you will need to register it with ChannelServices. ChannelServices needs to know the name of the object, its location, and how to communicate with it.

Registering the implemented objects is fairly simple. First, create a TcpServerChannel using any unused port number, and then register that channel with ChannelServices.

Next, use RemotingConfiguration.RegisterWellKnownServiceType to use CorporateAccountantExamFactory as its type, and IExamFactory as the name used remote objects.

The final parameter on this method call declares that this is a SingleCall object meaning that it should behave like a function library instead of an object. A SingleCall object is used only once and all member variables of the object go back to their default values as soon as the method call is completed (see the sidebar Understanding Singletons for more information about SingleCall objects). Here’s a partial listing that shows the code to register a channel. For a complete listing of the RemotingServer object, see Listing 3.

   TcpServerChannel chl =       new TcpServerChannel(9988);//The Address   ChannelServices.RegisterChannel(chl);      //Dropping the card in the box to register   RemotingConfiguration.      RegisterWellKnownServiceType(      typeof(CorporateAccountantExamFactory),//Name      "IExamFactory", //What the outside world calls it      WellKnownObjectMode.SingleCall);//How to contact

Client Side, An Exercise In Laziness
You can create a direct instance of the remote object, but doing so has some interesting implications. By directly creating the reference to the remote object you will have the “function library” effect. This is useful for high performance situations because the server does not have to keep every instance of the remote object in memory.

   ChannelServices.RegisterChannel(      new TcpChannel());   IAccountantTest directGrab =      (IAccountantTest)Activator.GetObject(      typeof(IAccountantTest),       "tcp://localhost:9988/IAccountantTest");   Console.WriteLine("Default:"+       directGrab.WhatIsTwoPlusTwo(4));   directGrab.SetName("Nadia");   Console.WriteLine("Set Name:"+       directGrab.WhatIsTwoPlusTwo(5));

The call to ChannelServices adds a new channel to the channel pool. You don’t need to know anything about this other than that it creates a channel that is transparently available to your remote objects.

You use the Activator object to create instances of objects when you don’t know at compile time exactly what objects the code requires. In Remoting, you use it because the actual code for the object exists somewhere else. In this case, the Activator object creates a reference to the object on the server. To create an instance, pass the Activator the Type object for the interface and the URL of the object you want to reference.

The output of the second call to WhatIsTwoPlusTwo() is where the interesting behavior lies. The code returns the string “David” even though the code in the preceding listing uses the SetName() method to set the name to “Nadia!” The problem is that the SingleCall object mode set on the server treats this object like a function library.

If the code had set the WellKnownObjectMode to Singleton on the server, it would have worked as expected; however then all clients accessing the object on that server would see receive a return value of “Nadia,” because only one instance of the object would exist on the server. Neither option provides the behavior you’re looking for. Ideally, you want your remote objects to behave like local objects. That’s where the factory comes in.

Creating the client through the factory interface will truly tickle the OO geek in you. When you call the CreateAccountTestInstance() method on the “function library like” instance of the factory object, it returns full fledged instances of objects that execute completely on the server.

   Object o = Activator.GetObject(      typeof(IAccountantExamFactory),       "tcp://localhost:9988/IExamFactory");   IAccountantExamFactory factory =       (IAccountantExamFactory)o;

Voila, you can now create a CorporateAccountantExamFactory using only the IAccountantExamFactory interface! The server implementation can create new versions of both the factory and the implemented objects without having to change any client side code whatsoever.

   IAccountantTest iat = factory.CreateAccountTestInstance()

IAccountantTest is the interface for CorporateAccountantTest. If at a later date you need to create a different implementation of the IAccountantTest interface, all you would have to do is have the CorporateAccountantExamFactory return the new type. Listing 4 contains a complete listing of this client.

Passing Objects By Value
Sometimes you want to work with a complete local copy of an object rather than a server-based reference like those you can create using the factory pattern. One reason you might want to do this is to reduce network traffic. For example, if you create an object that models a person’s address and make three separate calls to GetCity(), GetState() and GetZip() the code would make three separate round trips to the server over the network, which is very inefficient.

To have your object passed by value you must add the Serializable attribute to the class rather than inheriting serialization capability from MarshalByRefObject. However, you should be aware that doing so causes some changes in the way you distribute the application. You can only pass an object by value if it is a well-known object type. This means that the client has to have a copy of the DLL containing the implemented version of the object, not just the interface. That’s because the serialized version of the object doesn’t include any of the object’s method code?it contains only the object’s data.

Putting It All Together
You can download the complete source for this solution from the References column of this article. To run the code, open the solution file “DevXRemotingClients.sln” in Visual Studio.NET and compile and run it. The solution contains four projects.

  1. The Interfaces DLL?RemotingClient.exe
  2. The Implementation DLL?RemotingClientsInterfaces.dll
  3. The Server Object??RemotingServer.exe
  4. The Client Object??RemotingExampleImplementation.dll

I urge you to experiment with the projects. Modify the interface so it includes a get/set address. Create a serialized Address object to get a feel for how it works. Keep modifying it until it breaks?and then fix it!

.NET Remoting gives you a powerful tool for using remote objects across the network. Using the factory method described in this article, Remoting provides a simple and effective way to use full fledged remote objects instead of function libraries with complete separation between the interface of the client and the actual implementation on the server.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist