Chapter 17. .NET Remoting Quick start

17.1. Introduction

This quickstart demonstrates the basic usage of Spring.NET's remoting infrastructure; this infrastructure is comprised of a number of helper classes located in the Spring.Services namespace. This quickstart does assume some familiarity with .NET remoting on the part of the reader... if you are new to .NET remoting, you may find the links to introductory remoting material presented at the conclusion of this quickstart of some help.

17.2. The Remoting Sample Project

As usual with quick start examples in Spring.NET, the classes used in the quickstart are intentionally simple; in the specific case of this remoting quickstart, we are going to make a simple calculator that can be accessed remotely.

The example solution is located in the examples\Spring\Spring.Examples.Calculator directory and contains four separate projects.

The layout of the VS.NET remoting quickstart solution

The Interfaces project contains the interface ICalculator that defines the basic operations of a calculator as well as memory storage for results. (woo hoo - big feature - HP-12C beware!) The interface for the calculator's operations is shown below. The Server project contains an implementation of the ICalculator interface, RemotedCalculator, that inherits from the MarshalByRef class. The Client project contains the client application and the ServerApp project hosts the RemotedCalculator in a console application.

public interface ICalculator
{
    int Add(int num1, int num2);

    int Subtract(int num1, int num2);

    int Divide(int num1, int num2);

    int Multiply(int num1, int num2);

    void MemoryClear();

    int Memory
    {
        get;
        set;
    }

    void MemoryAdd(int num1);}

The structure of the VS.NET solution is a consequence of following the best practice of using interfaces to share type information between a .NET remoting client and server. The benefits of this approach are that the client does not need a reference to the assembly that contains the implementation class, because aving the client reference the implementation assembly is undesirable for a variety of reasons. One reason being security, since an untrusted client could potentially obtain the source code to the implementation since Intermediate Language (IL) code is easily reverse engineered. Another, more compelling, reason is to provide a greater decoupling between the client and server so the server can update its implementation of the interface in a manner that is quite transparent to the client; i.e. the client code need not change. Independent of .NET remoting best practices, using an interface to provide a service contract is just good object-oriented design. This lets the client choose another implementation unrelated to .NET Remoting, for example a local, test-stub or a web services implementation. One of the major benefits of using Spring.NET is that it reduces the cost of doing 'interface based programming' to almost nothing. As such, this best practice approach to .NET remoting fits naturally into the general approach to application development that Spring.NET encourages you to follow. Ok, with that barrage of OO design ranting finished, on to the implementation!

17.3. Implementation

The RemotedCalculator is hosted inside a console application as a singleton Server Activated Object (SAO). The implementation is quite straightforward; the only interesting methods are those that deal with the memory storage, which is the state that we will be configuring explicitly when using the SaoServiceExporter. These interesting methods are shown below.

public class RemotedCalculator : MarshalByRefObject, ICalculator
{
    private int memoryStore;
            
    public int Memory
    {
        get	{ return memoryStore; }
        set	{ 
            Console.WriteLine("old val = " + memoryStore + " new val = " + value);
            memoryStore = value; 
        }
    }

    public void MemoryClear()
    {
        memoryStore = 0;
    }
            
    public void MemoryAdd(int num1)
    {
        memoryStore += num1;
    }
}

The configuration of the .NET remoting channels is done using the standard system.runtime.remoting configuration section inside the .NET configuration file of the application (App.config). In this case we are using the tcp channel on port 8005.

<system.runtime.remoting>
  <application>
    <channels>
      <channel ref="tcp" port="8005" />		
    </channels>
  </application>
</system.runtime.remoting>

The console application implementation code is also quite simple.

public static void Main(string[] args)
{
    try 
    { 
        RemotingConfiguration.Configure("ServerApp.exe.config"); 1

        IApplicationContext ctx = ContextRegistry.GetContext(); 2

        write("\n\nServer started...");
        pause();
    }   
}

1 The standard means to initialize the .NET remoting services using the configuration file.
2 The initialization of Spring.NET's IoC container; this must occur after the initialization of the .NET remoting services.

The configuration of the calculator state, i.e. it's memory value, is done in the Spring.NET configuration section as shown below.

<objects xmlns="http://www.springframework.net">
    <description>An server example that demonstrates .NET remoting features.</description>
                
    <object name="myCalculator" type="Server.RemotedCalculator, Server">
    	<property name="Memory" value="10"/>
    </object>
    
    <object name="mySaoCalculator" type="Spring.Remoting.SaoServiceExporter, Spring.Services">
    	<property name="Service" ref="myCalculator"/>
    	<property name="ServiceName" value="MyRemotedCalculator"/>
    </object>	
                
</objects>

The declaration of the calculator instance, myCalculator, and the setting of any property values and / or object references is done as you would normally do for any object declared in the Spring.NET configuration file. The second step involves publishing this preconfigured object as a SAO singleton using the SaoServiceExporter. The SaoServiceExporter class exposes a number of properties that need to be set in order for the remoting of the object to succeed.

  • Service: a reference to the object that is to be remoted.

  • ServiceName: the name of the service as it will appear in the URL that clients use to locate the remote object.

In the specific case of this example, the value of the ServiceName is MyRemotedCalculator which, along with the channel configuration, will result in the remote object being identified by the URL tcp://localhost:8005/MyRemotedCalculator.

On the client side, the client application will connect to the remote calculator object, ask it for it's current memory value, which is pre-configured to 10, then perform a simple addition. As in the case of the server, the channel configuration is done using the standard .NET Remoting configuration section of the .NET application configuration file (App.config), as can been seen below.

<system.runtime.remoting>
    <application>
    	<channels>
    		<channel ref="tcp"/>
    	</channels>
    </application>
</system.runtime.remoting>

The client implementation code is shown below.

public static void Main(string[] args)
{
    RemotingConfiguration.Configure("Client.exe.config");
        
    pause();
    
    IApplicationContext ctx = ContextRegistry.GetContext();
        
    ICalculator calc = (ICalculator) ctx["saoCalculator"];

    write("Memory = " + calc.Memory);
    write("2 + 2 = " + calc.Add(2,2));
    write("setting memory to 4");
    calc.Memory = 4;
}

Note that the client application code is not aware that it is using a remote object. The pause() method simply waits until the Return key is pressed on the console so that the client doesn't make a request to the server before the server has had a chance to start. The standard configuration and initialization of the .NET remoting infrastructure is done before the creation of the Spring.NET IoC container, the configuration of which is shown below.

<objects xmlns="http://www.springframework.net">
    <description>A client example that demonstrates remoting features.</description>

    <object name="saoCalculator" type="Spring.Remoting.SaoFactoryObject, Spring.Services">
    	<property name="ServiceInterface" value="Interfaces.ICalculator, Interfaces" />
    	<property name="ServiceUrl" value="tcp://localhost:8005/MyRemotedCalculator" />
    </object>
                
</objects>

The SaoFactoryObject class is responsible for calling Activator.GetObject with the specified type and URL information. The property replacement facilities of Spring.NET can be leveraged here to make it easy to configure the URL value based on environment variable settings, a standard .NET configuration section, or an external property file. This is useful to easily switch between test, QA, and production (gasp!) environments. An example of how this would be expressed is...

<property name="ServiceUrl" value="${protocol}://${host}:${port}/MyRemotedCalculator" />

The property values in this example are defined elsewhere; refer to Section 3.8.1, “The PropertyPlaceholderConfigurer” for additional information. More important in terms of configuration flexibility is the fact that now you can swap out different implementations (both .NET remoting based or otherwise) of this interface by making a simple change to the configuration file.

17.4. Running the application

Now that we have had a walk though of the implementation and configuration it is finally time to run the application (if you haven't yet pulled the trigger). Be sure to set up VS.NET to run multiple applications on startup as shown below.

The startup configuration for the VS.NET solution

Running the solution yields the following output in the server window...

Server started...
--- hit <return> to continue ---

The corresponding output in the client window will look like this...

--- Client: hit <return> to continue ---

Hitting return in the client window will then access the remote calculator, showing that the initial memory state is set to 10. The client then adds two numbers and sets the memory on the remote calculator to 4. The client console output is shown below.

Memory = 10
2 + 2 = 4
setting memory to 4
SAO URI = tcp://localhost:8005/MyRemotedCalculator
 --- Client: hit <return> to continue ---

The corresponding output on the server is...

old val = 10 new val = 4

The URI associated with the object is printed out just for comparison purposes when obtaining a reference to the same object via the GetSaoObject method of the RemoteFactory. There you have it, the basic template for using SaoServiceExporter and SaoFactoryObject in your .NET remoting application.

17.5. Using the RemoteFactory

To obtain a CAO reference to the remote object we configure the server to contain a non-singleton, a.k.a, 'prototype' object definition as shown below.

<object name="prototypeCalculator" type="Server.RemotedCalculator, Server"
        singleton="false"/>

We then publish an instance of the Spring.Remoting.RemoteFactory using the SaoServiceExporter. This is shown below (ignore the declaration of the namedSaoCalculatorService object for now, as we'll get to that later).

<object name="namedSaoCalculatorService" type="Spring.Remoting.NamedServiceEntry">
  <property name="name" value="MyRemotedCalculatorFromFactory" />
  <property name="ServiceType" value="Server.RemotedCalculator, Server" />
  <property name="ServiceUri" value="tcp://localhost:8005/MyRemotedCalculator" />		  			  
</object>

<object name="remoteFactory" type="Spring.Remoting.RemoteFactory, Spring.Services">

    <property name="serviceEntries">
        <list>
            <ref object="namedSaoCalculatorService"/>
        </list>
    </property>
    
</object>

<object name="publishedRemoteFactory" type="Spring.Remoting.SaoServiceExporter, Spring.Services">
	<property name="Service" ref="remoteFactory"/>
	<property name="ServiceName" value="RemoteFactory"/>
</object>

With this configuration in place, the client can ask the remote factory for an object by the name of prototypeCalculator. The method CaoFactoryDemo(IApplicationContext ctx) in the client application demonstrates creating and using multiple CAO objects. The CAO transparent proxy URI is displayed to show that these objects are in fact independent instances hosted on the server. The following listing shows this for the first instance of a CAO calculator.

IRemoteFactory remoteFactory = (IRemoteFactory) ctx["remoteFactory"];
  ICalculator firstCalculator = remoteFactory.GetObject("prototypeCalculator") as ICalculator;

write("CAO#1 Memory = " + firstCalculator.Memory);
write("3 + 3 = " + firstCalculator.Add(3, 3));
write("CAO#1 Setting memory to 6");
firstCalculator.Memory = 6;
write("CAO#1 Memory = " + firstCalculator.Memory);

This results in the following output...

CAO#1 Memory = 0
3 + 3 = 6
CAO#1 Setting memory to 6
CAO#1 Memory = 6

A similar procedure is done for creating a second instance of the CAO object demonstrating that these objects hold independent state. The independence of the objects is also visible by printing the URI associated with each of the remote objects as shown below.

CAO#1 URI = /6ed4b505_1f3b_460d_992e_43c400da197f/jZvszYhX+Y5ObLlyY23dzMYh_1.rem

CAO#2 URI = /6ed4b505_1f3b_460d_992e_43c400da197f/l7aYmGP+KOEWWCrBJHCzb7eT_2.rem

This is the form of a URI reference for a CAO object, the only decipherable part being the trailing digits, _1 and _2 that are an implementation convention to enumerate the object instances.

We can also configure the RemoteFactory to return references to SAO objects by name. To do this we need to specify to the RemoteFactory the named used for lookup as well as the System.Type and URI that will be mapped onto that name. The class Spring.Remoting.NamedServiceEntry encapsulates this information and the RemoteFactory is configured with a list of these named entries. The server configuration specifying this entry as shown previously. On the client, the following code demonstrates how to obtain a reference to the SAO object (the client output follows immediately after)...

IRemoteFactory remoteFactory = (IRemoteFactory) ctx["remoteFactory"];

ICalculator calc = (ICalculator) remoteFactory.GetSaoObject("MyRemotedCalculatorFromFactory");

write("SAO Memory = " + calc.Memory);
write("SAO URI = " + RemotingServices.GetObjectUri((MarshalByRefObject)calc));
                
SAO Memory = 4
SAO URI = tcp://localhost:8005/MyRemotedCalculator

The value of "4" in the memory is the state from the previous operation on this calculator showing that it refers to the same singleton instance as obtained using the SaoFactoryObject. Displaying the URI for this object shows it has the same value as before.

17.6. Additional Resources

.NET remoting is a huge topic. Some introductory articles on .NET remoting can be found online at MSDN. Ingo Rammer is also a very good authority on .NET remoting, and the .NET Remoting FAQ (link below) which is maintained by Ingo is chock full of useful information.