Chapter 18. .NET Remoting Quick start

18.1. Introduction

This quickstart demonstrates the basic usage of Spring.NET's remoting infrastructure. The infrastructure classes are located in the Spring.Services assembly under the Spring.Services.Remotingnamespace. The overall strategy is to export .NET objects on the server side as either CAO or SAO objects using CaoExporter or SaoExporter and obtain references to these objects on the client side using CaoFactoryObject and SaoFactoryObject. This quickstart does assume 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.

18.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 same calculator class will be exported in multiple ways reflecting the variety of .NET remoting options available (CAO, SAO-SingleCall, SAO-Singleton) and also the use of adding AOP advice to SAO hosted objects.

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

The Interfaces project contains the interface ICalculator that defines the basic operations of a calculator and another interface IAdvancedCalculator that adds support for memory storage for results. (woo hoo - big feature - HP-12C beware!) These interfaces are shown below. The Services project contains an implementation of the these interfaces, namely the classes Calculator and AdvancedCalculator. The purpose of the AdvancedCalculator implementation is to demonstrate the configuration of object state for SAO-singleton objects. Note that the calculator implementations do not inherit from the MarshalByRefObject class. The Client project contains the client application and the RemoteServer project contains a console application that will host a remoted instance of the AdvancedCalculator class. The Aspects project contains some logging advice that will be used to demonstrate the application of aspects to remoted SAO objects. RegisterComponentServices is related to enterprise service exporters and is not relevant for this quickstart.

public interface ICalculator
{
    int Add(int n1, int n2);

    int Substract(int n1, int n2);

    double Divide(int n1, int n2);

    int Multiply(int n1, int n2);
}

An extension of this interface that supports having a slot for calculator memory is shown below

public interface IAdvancedCalculator : ICalculator
{
	int GetMemory();

	void SetMemory(int memoryValue);

	void MemoryClear();

	void MemoryAdd(int num);
}

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. Having 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!

18.3. Implementation

The implementation of the calculators contained in the Servies project 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 using constructor injection. A subset of the implementation is shown below.

public class Calculator : ICalculator
{

    public int Add(int n1, int n2)
    {
        return n1 + n2;
    }

    public int Substract(int n1, int n2)
    {
        return n1 - n2;
    }

    public double Divide(int n1, int n2)
    {
        return n1 / n2;
    }

    public int Multiply(int n1, int n2)
    {
        return n1 * n2;
    }

}

public class AdvancedCalculator : Calculator, IAdvancedCalculator
{

  private int memoryStore = 0;

  public AdvancedCalculator()
  {}

  public AdvancedCalculator(int initalMemory)
  {
    memoryStore = initalMemory;
  }

  public int GetMemory()
  {
    return memoryStore;
  }

  // other methods omitted in this listing...

}

The RemotedCalculator project hosts remoted objects inside a console application. The code is also quite simple and shown below

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

        IApplicationContext ctx = ContextRegistry.GetContext(); 2

        Console.Out.WriteLine("Server listening...");
    }   
    catch (Exception e)
    {
	Console.Out.WriteLine(e);
    }
    finally
    {
	Console.Out.WriteLine("--- Press <return> to quit ---");
	Console.ReadLine();
    }
}

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 .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 objects created in Spring's application context are shown below. Multiple resource files are used to export these objects under various remoting configurations. The AOP advice used in this example is a simple Log4Net based around advice.

<spring>
	<context>
		<resource uri="config://spring/objects" />
		<resource uri="assembly://RemoteServer/RemoteServer.Config/cao.xml" />
		<resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleCall.xml" />
		<resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleCall-aop.xml" />
		<resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleton.xml" />
		<resource uri="assembly://RemoteServer/RemoteServer.Config/saoSingleton-aop.xml" />
	</context>
	<objects xmlns="http://www.springframework.net">
		<description>Definitions of objects to be exported.</description>

		<object id="Log4NetLoggingAroundAdvice" type="Aspects.Logging.Log4NetLoggingAroundAdvice, Aspects">
			<property name="Level" value="Debug" />
		</object>

		<object id="singletonCalculator" type="Services.AdvancedCalculator, Services">
			<constructor-arg type="int" value="217"/>
		</object>
		
		<object id="singletonCalculatorWeaved" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop">
			<property name="target" ref="singletonCalculator"/>
			<property name="interceptorNames">
				<list>
					<value>Log4NetLoggingAroundAdvice</value>
				</list>
			</property>
		</object>
		
		<object id="prototypeCalculator" type="Services.AdvancedCalculator, Services" singleton="false">
			<constructor-arg type="int" value="217"/>
		</object>
		
		<object id="prototypeCalculatorWeaved" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop">
			<property name="targetSource">
				<object type="Spring.Aop.Target.PrototypeTargetSource, Spring.Aop">
					<property name="TargetObjectName" value="prototypeCalculator"/>
				</object>
			</property>
			<property name="interceptorNames">
				<list>
					<value>Log4NetLoggingAroundAdvice</value>
				</list>
			</property>
		</object>
		
	</objects>
</spring>

The declaration of the calculator instance, singletonCalculator for example, 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. To expose the calculator objects as .NET remoted objects the exporter Spring.Remoting.CaoExporter is used for CAO objects and Spring.Remoting.SaoExporter is used for SAO objeccts. Both exporters require the setting of a TargetName property that refers to the name of the object in Spring's IoC container that will be remoted. The semantics of SAO-SingleCall and CAO behavior are achieved by exorting a target object that is declared as a "prototype" (i.e. singleton=false). For SAO objects, the ServiceName property defines the the name of the service as it will appear in the URL that clients use to locate the remote object. To set the remoting lifetime of the objects to be infinite, the property Infinte is set to true.

The configuration for the exporting a SAO-Singleton is shown below.

<object name="saoSingletonCalculator" type="Spring.Remoting.SaoExporter, Spring.Services">
  <property name="TargetName" value="singletonCalculator" />
  <property name="ServiceName" value="RemotedSaoSingletonCalculator" />
  <property name="Infinite" value="true" />
</object>

This will result in the remote object being identified by the URL tcp://localhost:8005/RemotedSaoSingletonCalculator. The use of SaoExporter and CaoExporter for other configuration are similar, look at the configuration files in the RemoteServer project files for more information.

On the client side, the client application will connect a specific type of remote calculator service, object, ask it for it's current memory value, which is pre-configured to 217, 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)
{
	try
	{
	        pause();

		RemotingConfiguration.Configure("Client.exe.config");

		IApplicationContext ctx = ContextRegistry.GetContext();

		Console.Out.WriteLine("Get Calculator...");
		IAdvancedCalculator firstCalc = (IAdvancedCalculator) ctx.GetObject("calculatorService");
		Console.Out.WriteLine("Memory = " + firstCalc.GetMemory());
		firstCalc.MemoryAdd(2);
		Console.Out.WriteLine("Memory + 2 = " + firstCalc.GetMemory());

		Console.Out.WriteLine("Get Calculator...");
		IAdvancedCalculator secondCalc = (IAdvancedCalculator) ctx.GetObject("calculatorService");
		Console.Out.WriteLine("Memory = " + secondCalc.GetMemory());
	}
	catch (Exception e)
	{
		Console.Out.WriteLine(e);
	}
	finally
	{
		Console.Out.WriteLine("--- Press <return> to quit ---");
		Console.ReadLine();
	}
}

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 the client application is structed in such a way that one can easily switch implementations of the calculatorService retrieved from the application context. In more complex applications the calcuator service would be a dependency on another object in your application, say in a workflow processing layer. The following listing shows a configuration for use of a local implementation and then several remote implementations. The same Exporter approach can be used to create Web Services and Serviced Components (EnterpriseServices) of the calcualtor object but are not discussed in this QuickStart.

<spring>
	<context>
		<!-- Only one at a time ! -->
	
                <!-- ================================== -->
		<!-- In process (local) implementations -->
                <!-- ================================== -->
		<resource uri="assembly://Client/Client.Config.InProcess/inProcess.xml" />
		
                <!-- ======================== -->
		<!-- Remoting implementations -->
                <!-- ======================== -->

		<!-- Make sure 'RemoteServer' console application is running and listening. -->

		<!-- <resource uri="assembly://Client/Client.Config.Remoting/cao.xml" /> -->
		<!-- <resource uri="assembly://Client/Client.Config.Remoting/cao-ctor.xml" /> -->
		<!-- <resource uri="assembly://Client/Client.Config.Remoting/saoSingleton.xml" /> -->
		<!-- <resource uri="assembly://Client/Client.Config.Remoting/saoSingleton-aop.xml" /> -->
		<!-- <resource uri="assembly://Client/Client.Config.Remoting/saoSingleCall.xml" /> -->
		<!-- <resource uri="assembly://Client/Client.Config.Remoting/saoSingleCall-aop.xml" /> -->
		
		<!-- =========================== -->
		<!-- Web Service implementations -->
		<!-- =========================== -->
		<!-- Make sure 'http://localhost/SpringCalculator/' web application is running -->
		<!-- <resource uri="assembly://Client/Client.Config.WebServices/webServices.xml" /> -->
		<!-- <resource uri="assembly://Client/Client.Config.WebServices/webServices-aop.xml" /> -->
		
		<!-- ================================= -->
		<!-- EnterpriseService implementations -->
		<!-- ================================= -->
		<!-- Make sure you register components with 'RegisterComponentServices' console application.  -->
		<!-- <resource uri="assembly://Client/Client.Config.EnterpriseServices/enterpriseServices.xml" /> -->
	</context>
</spring>

The inProcess.xml configuration file creates an instance of AdvancedCalculator directly

<objects xmlns="http://www.springframework.net">
	
  <description>inProcess</description>
	
  <object id="calculatorService" type="Services.AdvancedCalculator, Services" />
	
</objects>

Factory classes are used to create a client side reference to the .NET remoting implementations. For SAO objects use the SaoFactoryObject class and for CAO objects use the CaoFactoryObject class. The configuration for obtaining a reference to the previously exported SAO singleton implementation is shown below

<objects xmlns="http://www.springframework.net">
	
	<description>saoSingleton</description>
	
	<object id="calculatorService" type="Spring.Remoting.SaoFactoryObject, Spring.Services">
		<property name="ServiceInterface" value="Interfaces.IAdvancedCalculator, Interfaces" />
		<property name="ServiceUrl" value="tcp://localhost:8005/RemotedSaoSingletonCalculator" />
	</object>
	
</objects>

You must specify the property ServiceInterface as well as the location of the remote object via the ServiceUrl property. 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 (yea baby!) environments. An example of how this would be expressed is...

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

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

The configuration for obtaining a reference to the previously exported CAO implementation is shown below

<objects xmlns="http://www.springframework.net">
	
	<description>cao</description>
	
	<object id="calculatorService" type="Spring.Remoting.CaoFactoryObject, Spring.Services">
		<property name="RemoteTargetName" value="prototypeCalculator" />
		<property name="ServiceUrl" value="tcp://localhost:8005" />
	</object>
	
</objects>

18.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.

Running the solution yields the following output in the server window... TO BE DONE...

18.5. 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.