Chapter 35. Portable Service Abstraction Quick Start

The Spring.NET Framework

Chapter 35. Portable Service Abstraction Quick Start

35.1. Introduction

This quickstart demonstrates the basic usage of Spring.NET's portable service abstraction functionality. Sections 2-5 demonstrate the use of .NET Remoting, Section 6 shows the use of the ServicedComponentExporter for .NET Enterprise Services, and Section 7 shows the use of the WebServiceExporter.

35.2. .NET Remoting Example

The infrastructure classes are located in the Spring.Services assembly under the Spring.Services.Remoting namespace. 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.

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.Calculator directory and contains multiple projects.

The Spring.Calculator.Contract 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 Spring.Calculator.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 Spring.Calculator.ClientApp project contains the client application and the Spring.Calculator.RemoteApp project contains a console application that will host a Remoted instance of the AdvancedCalculator class. The Spring.Aspects project contains some logging advice that will be used to demonstrate the application of aspects to remoted objects. Spring.Calculator.RegisterComponentServices is related to enterprise service exporters and is not relevant for this quickstart. Spring.Calculator.Web is related to web services exporters and is not relevant for this quickstart.

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

    int Subtract(int n1, int n2);

    DivisionResult Divide(int n1, int n2);

    int Multiply(int n1, int n2);
}

[Serializable]
public class DivisionResult
{
	  private int _quotient = 0;
	  private int _rest = 0;

	  public int Quotient
	  {
		  get { return _quotient; }
		  set { _quotient = value; }
	  }

	  public int Rest
	  {
		  get { return _rest; }
		  set { _rest = value; }
	  }
}

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!

35.3. Implementation

The implementation of the calculators contained in the Spring.Calculator.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 DivisionResult Divide(int n1, int n2)
    {
        DivisionResult result = new DivisionResult();
        result.Quotient = n1 / n2;
        result.Rest = n1 % n2;
        return result;
    }

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

}

public class AdvancedCalculator : Calculator, IAdvancedCalculator
{

  private int memoryStore = 0;

  public AdvancedCalculator()
  {}

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

  public int GetMemory()
  {
    return memoryStore;
  }

  // other methods omitted in this listing...

}

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

public static void Main(string[] args)
{
    try 
    { 
        // initialization of Spring.NET's IoC container
        IApplicationContext ctx = ContextRegistry.GetContext();

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

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.

	<configSections>
		<sectionGroup name="spring">
			<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
			<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
			<section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" />
		</sectionGroup>
		<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
	</configSections>

<spring>
		<parsers>
			<parser type="Spring.Remoting.Config.RemotingNamespaceParser, Spring.Services" />
		</parsers>
	<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 type="Spring.Remoting.RemotingConfigurer, Spring.Services">
      <property name="Filename" value="Spring.Calculator.RemoteApp.exe.config" />
    </object>
      
		<object id="Log4NetLoggingAroundAdvice" type="Spring.Aspects.Logging.Log4NetLoggingAroundAdvice, Spring.Aspects">
			<property name="Level" value="Debug" />
		</object>

		<object id="singletonCalculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.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="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.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 objects. 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 exporting a target object that is declared as a "prototype" (i.e. singleton=false). For SAO objects, the ServiceName property defines 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 Infinite is set to true.

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

<objects	
	xmlns="http://www.springframework.net" 
	xmlns:r="http://www.springframework.net/remoting">
	
	<description>Registers the calculator service as a SAO in 'Singleton' mode.</description>
	
	<r:saoExporter 
		targetName="singletonCalculator" 
		serviceName="RemotedSaoSingletonCalculator" />
</objects>

The configuration shown above uses the Spring Remoting schema but you can also choose to use the standard 'generic' XML configuration shown below.

<object name="saoSingletonCalculator" type="Spring.Remoting.SaoExporter, Spring.Services">
  <property name="TargetName" value="singletonCalculator" />
  <property name="ServiceName" value="RemotedSaoSingletonCalculator" />
</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 Spring.Calculator.RemotedApp 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();

		IApplicationContext ctx = ContextRegistry.GetContext();

		Console.Out.WriteLine("Get Calculator...");
		IAdvancedCalculator firstCalc = (IAdvancedCalculator) ctx.GetObject("calculatorService");
    Console.WriteLine("Divide(11, 2) : " + firstCalc.Divide(11, 2));
		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
	{
		Pause();
	}
}

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 constructed in such a way that one can easily switch implementations of the calculatorService retrieved from the application context. In more complex applications the calculator 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 (Enterprise Services) of the calculator object but are not discussed in this QuickStart.

<spring>
  <context>
    <resource uri="config://spring/objects" />
      
    <!-- Only one at a time ! -->

    <!-- ================================== -->
    <!-- In process (local) implementations -->
    <!-- ================================== -->
    <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.InProcess/inProcess.xml" />

    <!-- ======================== -->
    <!-- Remoting implementations -->
    <!-- ======================== -->
    <!-- Make sure 'RemoteApp' console application is running and listening. -->
    <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/cao.xml" /> -->
    <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/cao-ctor.xml" /> -->
    <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleton.xml" /> -->
    <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleton-aop.xml" /> -->
    <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleCall.xml" /> -->
    <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.Remoting/saoSingleCall-aop.xml" /> -->

    <!-- =========================== -->
    <!-- Web Service implementations -->
    <!-- =========================== -->
    <!-- Make sure 'http://localhost/SpringCalculator/' web application is running -->
    <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.WebServices/webServices.xml" /> -->
    <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.Config.WebServices/webServices-aop.xml" /> -->

    <!-- ================================= -->
    <!-- EnterpriseService implementations -->
    <!-- ================================= -->
    <!-- Make sure you register components with 'RegisterComponentServices' console application.  -->
    <!-- <resource uri="assembly://Spring.Calculator.ClientApp/Spring.Calculator.ClientApp.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="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.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="Spring.Calculator.Interfaces.IAdvancedCalculator, Spring.Calculator.Contract" />
		<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 5.9.2.1, “Example: 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>

35.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 and client window

  SERVER WINDOW

Server listening...
--- Press <return> to quit ---


   CLIENT WINDOW

--- Press <return> to continue ---     (hit return...)
Get Calculator...
Divide(11, 2) : Quotient: '5'; Rest: '1'
Memory = 0
Memory + 2 = 2
Get Calculator...
Memory = 2
--- Press <return> to continue ---

35.5. Remoting Schema

The spring-remoting.xsd file in the doc directory provides a short syntax to configure Spring.NET remoting features. To install the schema in the VS.NET environment run the install-schema NAnt script in the doc directory. Refer to the Chapter on VS.NET integration for more details.

The various configuration files in the RemoteServer and Client projects show the schema in action. Here is a condensed listing of those definitions which should give you a good feel for how to use the schema.

<!-- Calculator definitions -->
<object id="singletonCalculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services">
	<constructor-arg type="int" value="217" />
</object>

<object id="prototypeCalculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services" singleton="false">
	<constructor-arg type="int" value="217" />
</object>

<!-- CAO object -->
<r:caoExporter targetName="prototypeCalculator" infinite="false">
  <r:lifeTime initialLeaseTime="2m" renewOnCallTime="1m"/>
</r:caoExporter>

<!-- SAO Single Call -->
<r:saoExporter 
  targetName="prototypeCalculator" 
  serviceName="RemotedSaoSingleCallCalculator"/>

<!-- SAO Singleton -->
<r:saoExporter 
  targetName="singletonCalculator" 
  serviceName="RemotedSaoSingletonCalculator" />

Note that the singleton nature of the remoted object is based on the Spring object definition. The "PrototypeCalculator" has its singleton property set to false to that a new one will be created every time a method on the remoted object is invoked for the SAO case.

35.6. .NET Enterprise Services Example

The .NET Enterprise Services example is located in the project Spring.Calculator.RegisterComponentServices.2005.csproj or Spring.Calculator.RegisterComponentServices.2003.csproj, depending on the use of .NET 1.1 or 2.0. The example uses the previous AdvancedCalculator implementation and then imports the embedded configuration file 'enterpriseServices.xml' from the namespace Spring.Calculator.RegisterComponentServices.Config. The top level configuration is shown below

	<spring>
	
		<context>
			<resource uri="config://spring/objects" />
			<resource uri="assembly://Spring.Calculator.RegisterComponentServices/Spring.Calculator.RegisterComponentServices.Config/enterpriseServices.xml" />
		</context>
		
		<objects xmlns="http://www.springframework.net">
			<description>Definitions of objects to be registered.</description>
			
			<object id="calculatorService" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services" />
			
		</objects>
		
	</spring>

The exporter that adapts the AdvancedCalculator for use as an Enterprise Service component is defined first in enterpriseServices.xml. Second is defined an exporter that will host the exported Enterprise Services component application by signing the assembly, registering it with the specified COM+ application name. If application does not exist it will create it and configure it using values specified for Description, AccessControl and Roles properties. The configuration file for enterpriseServices.xml is shown below

<objects xmlns="http://www.springframework.net">

	<description>enterpriseService</description>
	
	<object id="calculatorComponent" type="Spring.EnterpriseServices.ServicedComponentExporter, Spring.Services">
		<property name="TargetName" value="calculatorService" />
		<property name="TypeAttributes">
			<list>
				<object type="System.EnterpriseServices.TransactionAttribute, System.EnterpriseServices" />
			</list>
		</property>
		<property name="MemberAttributes">
			<dictionary>
				<entry key="*">
					<list>
						<object type="System.EnterpriseServices.AutoCompleteAttribute, System.EnterpriseServices" />
					</list>
				</entry>
			</dictionary>
		</property>
	</object>
	
	<object type="Spring.EnterpriseServices.EnterpriseServicesExporter, Spring.Services">
		<property name="ApplicationName">
			<value>Spring Calculator Application</value>
		</property>
		<property name="Description">
			<value>Spring Calculator application.</value>
		</property>
		<property name="AccessControl">
			<object type="System.EnterpriseServices.ApplicationAccessControlAttribute, System.EnterpriseServices">
				<property name="AccessChecksLevel">
					<value>ApplicationComponent</value>
				</property>
			</object>
		</property>
		<property name="Roles">
			<list>
				<value>Admin : Administrator role</value>
				<value>User : User role</value>
				<value>Manager : Administrator role</value>
			</list>
		</property>
		<property name="Components">
			<list>
				<ref object="calculatorComponent" />
			</list>
		</property>
		<property name="Assembly">
			<value>Spring.Calculator.EnterpriseServices</value>
		</property>
	</object>
	
</objects>

35.7. Web Services Example

The WebServices example shows how to export the AdvancedCalculator as a web service that is an AOP proxy of AdvancedCalculator that has logging advice applied to it. The main configuration file, Web.config, includes information from three locations as shown below

		<context>
			<resource uri="config://spring/objects"/>
			<resource uri="~/Config/webServices.xml"/>
			<resource uri="~/Config/webServices-aop.xml"/>
		</context>

The config section 'spring/objects' in Web.config contains the definition for the 'plain' Advanced calculator, as well as the definitions to create an AOP proxy of an AdvancedCalculator that adds logging advice. These definitions are shown below

		<objects xmlns="http://www.springframework.net">
			
      <!-- Aspect -->
      
      <object id="CommonLoggingAroundAdvice" type="Spring.Aspects.Logging.CommonLoggingAroundAdvice, Spring.Aspects">
        <property name="Level" value="Debug"/>
      </object>
      
      <!-- Service -->
			
      <!-- 'plain object' for AdvancedCalculator -->
      <object id="calculator" type="Spring.Calculator.Services.AdvancedCalculator, Spring.Calculator.Services"/>
			
      <!-- AdvancedCalculator object with AOP logging advice applied. -->
      <object id="calculatorWeaved" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop">
				    <property name="target" ref="calculator"/>
				    <property name="interceptorNames">
					     <list>
						      <value>CommonLoggingAroundAdvice</value>
					     </list>
				    </property>
			    </object>
		
    </objects>

The configuration file webService.xml simply exports the named calculator object

	<object id="calculatorService" type="Spring.Web.Services.WebServiceExporter, Spring.Web">
		<property name="TargetName" value="calculator" />
		<property name="Namespace" value="http://SpringCalculator/WebServices" />
		<property name="Description" value="Spring Calculator Web Services" />
	</object>

Whereas the webService-aop.xml exports the calculator instance that has AOP advice applied to it.

	<object id="calculatorServiceWeaved" type="Spring.Web.Services.WebServiceExporter, Spring.Web">
		<property name="TargetName" value="calculatorWeaved" />
		<property name="Namespace" value="http://SpringCalculator/WebServices" />
		<property name="Description" value="Spring Calculator Web Services" />
	</object>
	

Setting the solution to run the web project as the startup, you will be presented with a screen as shown below

Selecting the CalculatorService and CalculatorServiceWeaved links will bring you to the standard user interface generated for browsing a web service, as shown below

And similarly for the calculator service with AOP applied

Invoking the Add method for calculatorServiceWeaved shows the screen

Invoking add will then show the result '4' in a new browser instance and the log file log.txt will contain the following entires

2007-10-15 17:59:47,375 [DEBUG] Spring.Aspects.Logging.CommonLoggingAroundAdvice - Intercepted call : about to invoke method 'Add'
2007-10-15 17:59:47,421 [DEBUG] Spring.Aspects.Logging.CommonLoggingAroundAdvice - Intercepted call : returned '4'

35.8. Additional Resources

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.