The Baker’s Dozen: 13 Productivity Tips for Remoting in Visual Studio 2005

his installment of the Baker’s Dozen presents an introduction to remoting and remoting interfaces.

Using .NET remoting you can call and execute code running outside your machine’s physical boundaries, such as a Web service accessing classes on an application server, or a client application accessing the middle tier in a client-server environment. The code is executed on the domain that stores the code.

Remoting is an ideal approach for executing code that resides on another domain: remoting interfaces allow developers to program against a strongly-typed model of code outside the current project. Building a remoting solution requires more code and may initially appear more complicated than Web services, so this article de-mystifies remoting by presenting a walk-through of a simple remoting project. The article begins with an overview of remoting architecture and the different ways to use remoting; then you’ll delve into the fundamental aspects of remoting. The article also presents some of the new capabilities for remoting in Visual Studio 2005. At the end, I provide a number of links for reading about more advanced remoting topics.

Beginning with the End in Mind
I’d been planning an article on remoting in Visual Studio 2005 for several months now. In the last few weeks, I’ve read different online questions that related directly to remoting. First, someone asked about practical uses for interfaces. Next, someone asked about the value of generics. Finally, someone asked why remoting is preferable to Web services. Those questions provided the inspiration for this article.

Remember to add a .NET reference to System.Runtime.Remoting any time you write code that directly accesses the remoting namespaces.

For the last year and a half, I’ve tried to target specific skill and experience levels for the Baker’s Dozen articles. This time around, I’ll preface by saying that this is an introductory article for those who want to see the following fundamentals of a remoting application, and how interfaces can help:

  • An overview of a remoting architecture
  • Different remoting implementations (TCP and HTTP)
  • Defining remoting interfaces
  • Defining the business object to be accessed through remoting
  • Defining the client piece
  • Defining the server (listener)
  • Improving DataSet remoting performance in Visual Studio 2005
  • Building a Windows service
  • New security enhancements for TCP remoting in Visual Studio 2005
  • An overview of generics in Visual Studio 2005
  • Using generics to simplify remoting interfaces
  • Creating a remoting object factory
  • Using the remoting object factory

Many of these tips also apply to Visual Studio 2003, unless specifically indicated as Visual Studio 2005. At the end of this article I’ll provide some good references for remoting. Here we go!

Tip 1: An Overview of Remoting Architecture
I’ll start with a very simple example. Let’s say that you have a local class (client class) that needs to call another class in the middle-tier (external method). The external method retrieves customer data based on a customer key. The client class and remote method reside on different application boundaries, so the client class neither sees the external method that retrieves the data as a project file nor as a reference. When the client class executes the external method, the code in the external method must execute on the domain where the external method resides.

Your first inclination might be to dynamically search and load the external method through reflection. Although that’s possible, the approach relies on assumptions, such as availability of the module, name and location, etc.

Solve the problem by defining a strongly-typed remoting interface that both the calling project and external method can use. Use TCP remoting to communicate across application boundaries and access the external method. A strongly-typed approach is more efficient and less likely to introduce runtime errors.

As an overview, the steps to accomplish this are as follows:

  1. Create a strongly typed interface (ICustomer) that contains a definition for a GetCustomer method.
  2. Create a base business object (BaseBzObject) class from which the external method inherits. The base business object itself inherits from System.MarshalByRefObject, which is required for remoting.
  3. Create a Customer Business object (CustomerBzObject) that does three things: inherits from BaseBzObject, implements ICustomer, and contains a GetCustomer method.
  4. Build a “listener,” a remoting server that registers CustomerBzObject. The client class can access and activate registered classes through a TCP port.
  5. Create a simple client form that activates an object using a type reference to the ICustomer interface through the same TCP port that the remoting server/listener monitors.

These five steps represent the “what.” Tips three through seven cover the “how” and “why.”

Tip 2: Understanding the Different Options in Remoting
There are two flavors of remoting: TCP and HTTP. That gives us a grand total of three different options for distributed computing when you add .NET Web services into the mix. Note that they’re not mutually exclusive: an application might use Web services between the client and application tiers and remoting inside the application tiers.

So let’s take a moment and look at the three, and common reasons that they’re used:

  • ASP.NET Web services are the way to go when you must support SOAP over HTTP, and/or you have non-.NET applications accessing the application.
  • HTTP remoting is an option for installations that always use .NET for all layers, but also must make use of HTTP. You can set up your remoting components under IIS to use authentication and security features.
  • TCP remoting in Visual Studio 2003 was an excellent choice for internal applications that didn’t require security. TCP remoting is generally (and in some instances, significantly) the fastest of the three. Visual Studio 2005 also extends TCP remoting by offering security capabilities (see Tip 9). This makes TCP remoting even more attractive for applications beyond internal ones.

Visual Studio 2005 enhances remoting even further by allowing developers to serialize datasets across application boundaries in a binary format (see Tip 7). This eliminates the need to programmatically convert DataSets to or from XML strings, and reduces the size of the dataset when you pass it across physical boundaries.

Tip 3: Building Remoting Interfaces
As stated in Tip 1, the first step is to create an interface. The external class that you wish to access implements this interface. The client piece accesses the external class by means of this interface.

   using System;   using System.Text;   using System.Data;   namespace SimpleInterfaces   {      public interface ICustomer      {         DataSet GetCustomer(int AccountID);         // we could have more, like a          // SaveData method         //void SaveData(DataSet CustomerData);          }   }

Note that the interface contains no actual code for GetCustomer. The only significance is that any class implementing this interface must have a method called GetCustomer that receives an account ID and returns a DataSet. As you’ll see later, that’s all the client side needs.

Tip 4: Building the Back-end Remoting Objects
The next step is to build the external business object. In a nutshell, the external class must do three things:

  1. Implement ICustomer
  2. Inherit from a .NET system class called MarshalByRefObject, which enables remote code to execute the class on the domain of the external method
  3. Inherit from a base business object

This immediately presents a challenge: a single class can inherit from one class and one interface, but it cannot inherit from multiple classes. You can resolve this by defining a base business object (BaseBzObject) that inherits from MarshalByRefObject. Then you can define the actual business object (SimpleCustomerBzObject) to inherit from BaseBzObject and implement ICustomer.

Carl Franklin (DotNetRocks) provides an excellent example of the value of generics-a great way to simplify multiple classes that differ by an internal type.

This way, SimpleCustomerBzObject still picks up the critical inheritance of MarshalByRefObject from the BaseBzObject.

Here is the code for BaseBzObject:

   using System;   using System.Collections.Generic;   using System.Text;   namespace SimpleBaseBzObject   {      public class BaseBzObject :          System.MarshalByRefObject      {         // Base business object methods go here      }   }

Here is the code for SimpleCustomerBzObject:

   using System;   using System.Collections.Generic;   using System.Text;   using System.Data;   using SimpleBaseBzObject;   using SimpleInterfaces;   namespace SimpleCustomerBzObject    {      public class CustomerBzObject : BaseBzObject,          ICustomer      {         public DataSet GetCustomer(int AccountID)         {               // do something, return a dataset         }      }   }

Tip 5: Building the Client Piece
Now that you’ve established an interface and a server-side business object, you need to write code to access this back-end object.

As stated earlier, the client piece has neither the code nor the reference to the customer business object. All you have is the interface; as you’re about to find out, that (and a port number and address) is all you need.

The following code snippet demonstrates how to access the external class.

   using System.Runtime.Remoting;   using System.Runtime.Remoting.Channels;   using System.Runtime.Remoting.Channels.Tcp;   using SimpleInterfaces;   ICustomer oRemoteCustomer;   Type tCustomer = typeof(ICustomer);   ChannelServices.RegisterChannel( new       TcpClientChannel());   oRemoteCustomer =       (ICustomer)Activator.GetObject(  tCustomer,        "tcp://localhost:8228/CustomerBzObject");   DataSet DsTemp = oRemoteCustomer.GetCustomer(123);

The code does the following:

  • Adds a reference to the System.Runtime.Remoting namespace (not shown)
  • Includes several .NET remoting namespaces, as well as the interface class
  • Defines an object reference to the ICustomer interface. You’ll eventually use the object reference to access the back-end method GetCustomer, as if you had direct access to it.
  • Defines a type reference to ICustomer (the server-side needs this to match up on object types)
  • Opens a TCP channel (for purposes of demonstration, you’re hard-coding port 8228 and the TCP address)
  • Activates the remote object and casts the return value to ICustomer. At this point, oRemoteCustomer can access any properties and methods defined in ICustomer.
?
Figure 1: Client-side remoting uses a strongly-typed Interface.

Perhaps most important of all, you did not use reflection to accomplish any of this. You were able to build a strongly-typed solution. Figure 1 demonstrates that at design-time, all methods that ICustomer implements are shown in IntelliSense. Challenge yourself and your colleagues to use strongly typed elements as much as possible. Design-time discovery and type-safe checking are valuable, especially in a multi-developer environment.

Tip 6: Building the Remoting Server Listener
There’s just one remaining piece: on the domain that contains the method code for the business object, you need to build a listener. The listener checks the specified port in the client code and registers the business object for remote access:

   using System.Runtime.Remoting;   using System.Runtime.Remoting.Channels;   using System.Runtime.Remoting.Channels.Tcp;   TcpServerChannel Tcps;   int nTCPPort = 8228;   Tcps = new TcpServerChannel(nTCPPort);   ChannelServices.RegisterChannel(Tcps);   RemotingConfiguration.RegisterWellKnownServiceType(      typeof(SimpleCustomerBzObject.CustomerBzObject),      "CustomerBzObject", WellKnownObjectMode.Singleton);

Although the code appears a bit esoteric, the method names are self-explanatory. The domain that contains the business object registers a TCP port channel and CustomerBzObject. As you look back to the client-side code, you can see how the object type references must match.

Tip 7: Improving DataSet Remoting Performance in Visual Studio 2005
Many data-driven applications pass DataSets from one physical boundary to another. Because DataSets contain a large amount of overhead, developers often convert them to or from XML strings to reduce the transfer overhead.

Fortunately, Visual Studio 2005 improves performance by allowing developers to serialize DataSets as binary files.

   DsCustomer.RemotingFormat =       SerializationFormat.Binary;

Tip 8: Building a Windows Service
Tip 7 presented the code from a basic Windows form application to listen for remote calls. In reality, most installations run a listener as a Windows service.

Visual Studio 2003 and Visual Studio 2005 both allow you to build Windows services. Some developers characterize the process as “so simple that it’s scary.” Although you need to know a little about setting up the entities of a service (the process installer, etc.), fortunately, there’s a large amount of online information about building Windows services in .NET.

The download for this article contains a separate project for a Windows service.

Tip 9: New Security Enhancements for TCP Remoting in Visual Studio 2005
In Visual Studio 2003, you had to host HTTP remoting objects under IIS to gain any measure of security. Visual Studio 2005 contains enhancements to the TCP channel, so that you can configure secure TCP communications. Specifically, TCP remoting supports encryption and authentication using Security Support Provider Interface (SSPI).

Challenge yourself and your colleagues to use strongly typed elements as much as possible, using reflection only when necessary.

On the client piece, you can define a simple collection containing all the necessary security settings and then pass the collection to the constructor of the TCP channel:

   Dictionary oDict =       new Dictionary();   oDict.Add("secure", "true");   TcpChannel oChannel =       new TcpChannel(oDict, null, null);   ChannelServices.RegisterChannel(oChannel);

On the server side, you need to set the second parameter (ensureSecurity) to true:

   Tcps = new TcpServerChannel(nTCPPort);   ChannelServices.RegisterChannel(Tcps,true);

Tip 10: An Overview of Generics
Generics are one of the hottest new language features in Visual Studio 2005. Generics are often described in terms of what the previous version of .NET didn’t support. A common example is collections, where you stored references to any type of object and then performed a cast to retrieve the actual value of an index within the collection. Unfortunately, you surrender the element of compile-time type safety, and this casting incurs a small performance penalty because of the boxing and unboxing that occurs.

The new Visual Studio 2005 namespace Systems.Generic.Collections allows a developer to create a collection and specify the permitted member types; consequently, the compiler only permits you to add objects of the types previously declared. You no longer need to perform cast operations to retrieve a value from a collection.

At the MSDN CodeCamp in New Jersey last fall, Carl Franklin gave one of the best short explanations of generics that I’ve heard (see www.DotNetRocks.com ). He said: “Generics are a great way to simplify multiple classes that differ by an internal type.” This is a segue to my next tip.

Tip 11: The Baker’s Spotlight: Applying Generics to Simplify Remoting Interfaces
The very simple example contained one interface and one method. Let’s think about a real-world application for a moment. You could have a dozen modules, each containing a method to retrieve data for a primary key. That might mean GetProduct, GetAccount, GetCostCenter, etc. And then you might have other categories of functions for the various modules. Does that mean you’re potentially facing a proliferation of interfaces?

In Visual Studio 2005, the answer is: not necessarily. Applying Carl Franklin’s observation that generics can simplify classes that differ by an internal type, you could define a generic interface that receives a single primary key integer (it could be an account key or a product key), and returns a dataset (it could be account results or product results).

   using System;   using System.Collections.Generic;   using System.Text;   using System.Data;   namespace GenericInterfaces   {      public interface IRemoteGenericResultSet      {         DataSet GetResults(int nID);          }   }

Note the syntax: the placeholder indicates that this is a generic. When you refer to the actual Type, you will represent it in the same way. The following code demonstrates a reference to an object that implements this generic interface:

   string cServer = "tcp://localhost:8228";   object oRemoteObject =      Activator.GetObject(   typeof(IRemoteGenericResultSet),      cServer + "/GetCustomers");   IRemoteGenericResultSet oCustomer =    oRemoteObject as       IRemoteGenericResultSet;   oCustomer.GetResults(111);

Note that the calling procedure doesn’t contain any type references to a specific module. There is the literal for the name of the back-end object, which you could easily place into a string or property.

This is one of many ways generics can simplify programming. I’ll devote a future Baker’s Dozen article to generics in detail.

Tip 12: Creating a Remoting Object Factory
Another way you can simplify remoting is to abstract the details of remoting to a separate class. Listing 1 contains the code for a remoting object factory. If an application needs to access many remote objects, you can instantiate this factory, set the properties for the port, server, interface, and type reference to the desired back-end object, and then case the return value to the interface you need.

Tip 13: Using the remoting object factory
Here is a simple example of using the factory:

   using SimpleInterfaces;   RemotingFactory oFactory = new RemotingFactory();   oFactory.nTCPPort = 8228;   oFactory.cTcpServer = "tcp://localhost";   oFactory.cRemoteObjectName  = "CustomerBzObject";   oFactory.tInterface = typeof(ICustomer);   ICustomer oRemoteCustomer;   oRemoteCustomer =         (ICustomer)oFactory.GetRemoteInterfaceObject();   DataSet DsTemp= oRemoteCustomer.GetCustomer(111);

Note that no actual remoting code exists in the snippet above, other than setting address properties that are merely for demonstration. The key point here is that the remoting object factory handles all the complexities of remoting.

The Baker’s Dozen Commentary
I’ve learned a great deal over the last year about developers’ reactions towards remoting. Many developers avoid remoting because of perceived complexities. I set out to demonstrate that although a few additional steps are necessary, you can abstract out those steps from the rest of your application. In the very beginning, I found remoting to be a bit complex but became comfortable after a bit of study. I now use it whenever possible.

I’ve also been pleasantly surprised at the number of developers who use remoting. And although some have adopted a position of waiting for Indigo and other coming technologies, many developers need to know how to solve application issues now. Incidentally, that’s one of the goals of the Baker’s Dozen: to demonstrate how to use technology that exists today.

Closing Thoughts:
Hopefully this step-by-step approach served to explain remoting, as well as demonstrated the benefits of strongly typed development. Even with the new capabilities of future Microsoft development technologies (Indigo), many will continue to use remoting, at least for the near-term.

Article Source Code
You can find the entire source code for this article on my Web site. For additional information, check out my blog.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: