![]() |
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
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
![]() The 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
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 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> <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= The declaration of the calculator instance,
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
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 <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 <spring> <context> <resource uri="config://spring/objects" /> <!-- Only one at a time ! --> <!-- ================================== --> <!-- In process (local) implementations --> <!-- ================================== --> <resource The inProcess.xml configuration file creates an instance of AdvancedCalculator directly <objects xmlns="http://www.springframework.net"> <description>inProcess</description> <object id="calculatorService" type= Factory classes are used to create a client side reference to the
.NET remoting implementations. For SAO objects use the
<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 <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= 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= 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= 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= 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.
|