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