Chapter 3: Remoting in Action

IN THIS CHAPTER, I DEMONSTRATE the key techniques you'll need to know to use .NET Remoting in your real-world applications. I show you the differences between Singleton and SingleCall objects and untangle the mysteries of client-activated objects. I also introduce you to SoapSuds.exe, which can be used to generate proxy objects containing only methods' stubs. This chapter is somewhat code based, so prepare yourself to start VS .NET quite often!

Types of Remoting

As you have seen in the previous chapter's examples, there are two very different types of remote interaction between components. One uses serializable objects that are passed as a copy to the remote process. The second employs server-side (remote) objects that allow the client to call their methods.

ByValue Objects

Marshalling objects by value means to serialize their state (instance variables), including all objects referenced by instance variables, to some persistent form from which they can be deserialized in a different context. This ability to serialize objects is provided by the .NET Framework when you set the attribute [Serializable] for a class or implement ISerializable.

When passing the Customer object in the previous chapter's validation example to the server, it is serialized to XML like this:

 <a1:Customer id='ref-4'> <FirstName id='ref-5'>Joe</FirstName> <LastName id='ref-6'>Smith</LastName> <DateOfBirth>1800-05-12T00:00:00.0000+02:00</DateOfBirth> </a1:Customer> 

This XML document will be read by the server and an exact copy of the object created.

Note 

An important point to know about ByValue objects is that they are not remote objects. All methods on those objects will be executed locally (in the same context) to the caller. This also means that, unlike with MarshalByRefObjects, the compiled class has to be available to the client. You can see this in the preceding snippet, where "age" is not serialized but will be recalculated at the client using the getAge() method.

When a ByValue object holds references to other objects, those have to be either serializable or MarshalByRefObjects; otherwise, an exception will be thrown, indicating that those objects are not remoteable.

MarshalByRefObjects

A MarshalByRefObject is a remote object that runs on the server and accepts method calls from the client. Its data is stored in the server's memory and its methods executed in the server's AppDomain. Instead of passing around a variable that points to an object of this type, in reality only a pointer-like construct-called an ObjRef-is passed around. Contrary to common pointers, this ObjRef does not contain the memory address, rather the server name/IP address and an object identity that identifies exactly one object of the many that are probably running on the server. I cover this in depth later in this chapter. MarshalByRefObjects can be categorized into two groups: server-activated objects (SAOs) and client-activated objects (CAOs).

Server-Activated Objects

Server-activated objects are somewhat comparable to classic stateless Web Services. When a client requests a reference to a SAO, no message will travel to the server. Only when methods are called on this remote reference will the server be notified.

Depending on the configuration of its objects, the server then decides whether a new instance will be created or an existing object will be reused. SAOs can be marked as either Singleton or SingleCall. In the first case, one instance serves the requests of all clients in a multithreaded fashion. When using objects in SingleCall mode, as the name implies, a new object will be created for each request and destroyed afterwards.

In the following examples, you'll see the differences between these two kinds of services. You'll use the same shared interface, clientand server-side implementation of the service, and only change the object mode on the server.

The shared assembly General.dll will contain the interface to a very simple remote object that allows the storage and retrieval of stateful information in form of an int value, as shown in Listing 3-1.

Listing 3-1: The Interface Definition That Will Be Compiled to a DLL

start example
 using System; namespace General {    public interface IMyRemoteObject    {       void setValue (int newval);       int getValue();    } } 
end example

The client that is shown in Listing 3-2 provides the means for opening a connection to the server and tries to set and retrieve the instance values of the server-side remote object. You'll have to add a reference to System.Runtime.Remoting.DLL to your Visual Studio .NET project for this example.

Listing 3-2: A Simple Client Application

start example
 using System; using System.Runtime.Remoting; using General; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Remoting.Channels; namespace Client {    class Client    {       static void Main(string[] args)       {          HttpChannel channel = new HttpChannel();          ChannelServices.RegisterChannel(channel);          IMyRemoteObject obj = (IMyRemoteObject) Activator.GetObject(             typeof(IMyRemoteObject),             "http://localhost:1234/MyRemoteObject.soap");          Console.WriteLine("Client.Main(): Reference to rem. obj acquired");          int tmp = obj.getValue();          Console.WriteLine("Client.Main(): Original server side value: {0}",tmp);          Console.WriteLine('Client.Main(): Will set value to 42");          obj.setValue(42);          tmp = obj.getValue();          Console.WriteLine("Client.Main(): New server side value {0}", tmp);          Console.ReadLine();       }    } } 
end example

The sample client will read and output the server's original value, change it to 42, and then read and output it again.

SingleCall Objects

For SingleCall objects the server will create a single object, execute the method, and destroy the object again. SingleCall objects are registered at the server using the following statement:

 RemotingConfiguration.RegisterWellKnownServiceType(          typeof(<YourClass>), "<URL>",          WellKnownObjectMode.SingleCall); 

Objects of this kind can obviously not hold any state information, as all internal variables will be discarded at the end of the method call. The reason for using objects of this kind is that they can be deployed in a very scalable manner. These objects can be located on different computers with an intermediate multiplexing/load-balancing device, which would not be possible when using stateful objects. The complete server for this example can be seen in Listing 3-3.

Listing 3-3: The Complete Server Implementation

start example
 using System; using System.Runtime.Remoting; using General; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Remoting.Channels; namespace Server {    class MyRemoteObject: MarshalByRefObject, IMyRemoteObject    {       int myvalue;       public MyRemoteObject()       {          Console.WriteLine("MyRemoteObject.Constructor: New Object created");       }       public MyRemoteObject(int startvalue)       {          Console.WriteLine("MyRemoteObject.Constructor: .ctor called with {0}",                                startvalue);          myvalue = startvalue;       }       public void setValue(int newval)       {          Console.WriteLine("MyRemoteObject.setValue(): old {0} new {1}",                                myvalue,newval);          myvalue = newval;       }       public int getValue()       {          Console.WriteLine("MyRemoteObject.getValue(): current {0}",myvalue);          return myvalue;       }    }    class ServerStartup    {       static void Main(string[] args)       {          Console.WriteLine ("ServerStartup.Main(): Server started");          HttpChannel chnl = new HttpChannel(1234);          ChannelServices.RegisterChannel(chnl);          RemotingConfiguration.RegisterWellKnownServiceType(                    typeof(MyRemoteObject),                    "MyRemoteObject.soap",                    WellKnownObjectMode.SingleCall);          // the server will keep running until keypress.          Console.ReadLine();       }    } } 
end example

When the program is run, the output in Figure 3-1 will appear on the client.

click to expand
Figure 3-1: Client's output for a SingleCall object

What's happening is exactly what you'd expect from the previous description-even though it might not be what you'd normally expect from an object-oriented application. The reason for the server returning a value of 0 after setting the value to 42 is that your client is talking to a completely different object. Figure 3-2 shows the server's output.

click to expand
Figure 3-2: Server's output for a SingleCall object

This indicates that the server will really create one object for each call (and an additional object during the first call as well).

Singleton Objects

Only one instance of a Singleton object can exist at any given time. When receiving a client's request, the server checks its internal tables to see if an instance of this class already exists; if not, this object will be created and stored in the table. After this check the method will be executed. The server guarantees that there will be exactly one or no instance available at a given time.

Note 

Singletons have an associated lifetime as well, so be sure to override the standard lease time if you don't want your object to be destroyed after some minutes. (More on this later in this chapter.)

For registering an object as a Singleton, you can use the following lines of code:

 RemotingConfiguration.RegisterWellKnownServiceType(          typeof(<YourClass>), "<URL>",          WellKnownObjectMode.Singleton); 

The ServerStartup class in your sample server will be changed accordingly:

 class ServerStartup {    static void Main(string[] args)    {       Console.WriteLine ("ServerStartup.Main(): Server started");       HttpChannel chnl = new HttpChannel(1234);       ChannelServices.RegisterChannel(chnl);       RemotingConfiguration.RegisterWellKnownServiceType(                 typeof(MyRemoteObject),                 "MyRemoteObject.soap",                 WellKnownObjectMode.Singleton);       // the server will keep running until keypress.       Console.ReadLine();    } } 

When the client is started, the output will show a behavior consistent with the "normal" object-oriented way of thinking; the value that is returned is the same value you set two lines before (see Figure 3-3).

click to expand
Figure 3-3: Client's output for a Singleton object

The same is true for the server, as Figure 3-4 shows.

click to expand
Figure 3-4: Server's output for a Singleton object

An interesting thing happens when a second client is started afterwards. This client will receive a value of 42 directly after startup without your setting this value beforehand (see Figures 3-5 and 3-6). This is because only one instance exists at the server, and the instance will stay alive even after the first client is disconnected.

click to expand
Figure 3-5: The second client's output when calling a Singleton object

click to expand
Figure 3-6: Server's output after the second call to a Singleton object

Tip 

Use Singletons when you want to share data or resources between clients.

Published Objects

When using either SingleCall or Singleton objects, the necessary instances will be created dynamically during a client's request. When you want to publish a certain object instance that's been precreated on the server-for example, one using a nondefault constructor-neither alternative provides you with a solution.

In this case you can use RemotingServices.Marshal() to publish a given instance that behaves like a Singleton afterwards. The only difference is that the object has to already exist at the server before publication.

 YourObject obj = new YourObject(<your params for constr>); RemotingServices.Marshal(obj,"YourUrl.soap"); 

The code in the ServerStartup class will look like this:

 class ServerStartup {    static void Main(string[] args)    {       Console.WriteLine ("ServerStartup.Main(): Server started");       HttpChannel chnl = new HttpChannel(1234);       ChannelServices.RegisterChannel(chnl);       MyRemoteObject obj = new MyRemoteObject(4711);       RemotingServices.Marshal(obj,"MyRemoteObject.soap");       // the server will keep running until keypress.       Console.ReadLine();    } } 

When the client is run, you can safely expect to get a value of 4711 on the first request because you started the server with this initial value (see Figures 3-7 and 3-8).

click to expand
Figure 3-7: Client's output when calling a published object

click to expand
Figure 3-8: Server's output when publishing the object

Client-Activated Objects

A client-activated object (CAO) behaves mostly the same way as does a "normal" .NET object (or a COM object). When a creation request on the client is encountered (using Activator.CreateInstance() or the new operator), an activation message is sent to the server, where a remote object is created. On the client a proxy that holds the ObjRef to the server object is created like it is with SAOs.

A client-activated object's lifetime is managed by the same lifetime service used by SAOs, as shown later in this chapter. CAOs are so-called stateful objects; an instance variable that has been set by the client can be retrieved again and will contain the correct value.[1] These objects will store state information from one method call to the other. CAOs are explicitly created by the client, so they can have distinct constructors like normal .NET objects do.

Direct/Transparent Creation

The .NET Remoting Framework can be configured to allow client-activated objects to be created like normal objects using the new operator. Unfortunately, this manner of creation has one serious drawback: you cannot use shared interfaces or base classes. This means that you either have to ship the compiled objects to your clients or use SoapSuds to extract the metadata.

As shipping the implementation to your clients is neither feasible due to deployment and versioning issues nor in support of the general idea of distributed applications, I refrain from delving heavily into this approach here. Unfortunately, it's not currently possible to call nondefault constructors when using SoapSuds-generated metadata. When your application needs this functionality, you might choose the class factory-based approach (which is shown after this example) or rely on SoapSuds' -gc parameter to manually enhance the generated proxy (more on this parameter in Chapter 4).

In the following example, you'll use more or less the same class you did in the previous examples; it will provide your client with a setValue() and getValue() method to store and retrieve an int value as the object's state. The metadata that is needed for the client to create a reference to the CAO will be extracted with SoapSuds.exe, about which you'll read more later in this chapter.

The reliance on SoapSuds allows you to develop the server application without any need for up-front design of a shared assembly, therefore the server will simply include the CAOs implementation. You can see this in Listing 3-4.

Listing 3-4: A Server That Offers a Client-Activated Object

start example
 using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Remoting.Channels; namespace Server {    public class MyRemoteObject: MarshalByRefObject    {       int myvalue;       public MyRemoteObject(int val)       {          Console.WriteLine("MyRemoteObject.ctor(int) called");          myvalue = val;       }       public MyRemoteObject()       {          Console.WriteLine("MyRemoteObject.ctor() called");       }       public void setValue(int newval)       {          Console.WriteLine("MyRemoteObject.setValue(): old {0} new {1}",                               myvalue,newval);          myvalue = newval;       }       public int getValue()       {          Console.WriteLine("MyRemoteObject.getValue(): current {0}",myvalue);          return myvalue;       }    }    class ServerStartup    {       static void Main(string[] args)       {          Console.WriteLine ("ServerStartup.Main(): Server started");          HttpChannel chnl = new HttpChannel(1234);          ChannelServices.RegisterChannel(chnl);          RemotingConfiguration.ApplicationName = "MyServer";          RemotingConfiguration.RegisterActivatedServiceType(                                  typeof(MyRemoteObject));          // the server will keep running until keypress.          Console.ReadLine();       }    } } 
end example

On the server you now have the new startup code needed to register a channel and this class as a client-activated object. When adding a Type to the list of activated services, you cannot provide a single URL for each object; instead, you have to set the RemotingConfiguration.ApplicationName to a string value that identifies your server.

The URL to your remote object will be http://<hostname>:<port>/<ApplicationName>. What happens behind the scenes is that a general activation SAO is automatically created by the framework and published at the URL http://<hostname>:<port>/<ApplicationName>/RemoteActivationService.rem. This SAO will take the clients' requests to create a new instance and pass it on to the remoting framework.

To extract the necessary interface information, you can run the following SoapSuds command line in the directory where the server.exe assembly has been placed:

 soapsuds -ia:server -nowp -oa:generated_metadata.dll 
Note 

You should perform all command-line operations from the Visual Studio command prompt, which you can bring up by selecting Start Programs Microsoft Visual Studio .NET Visual Studio .NET Tools. This command prompt sets the correct "path" variable to include the .NET SDK tools.

The resulting generated_metadata.dll assembly must be referenced by the client. The sample client also registers the CAO and acquires two references to (different) remote objects. It then sets the value of those objects and outputs them again, which shows that you really are dealing with two different objects.

As you can see in Listing 3-5, the activation of the remote object is done with the new operator. This is possible because you registered the Type as ActivatedClientType before. The runtime now knows that whenever your application creates an instance of this class, it instead should create a reference to a remote object running on the server.

Listing 3-5: The Client Accesses the Client-Activated Object

start example
 using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Activation; using Server; namespace Client {    class Client    {       static void Main(string[] args)       {          HttpChannel channel = new HttpChannel();          ChannelServices.RegisterChannel(channel);          RemotingConfiguration.RegisterActivatedClientType(                typeof(MyRemoteObject),                "http://localhost:1234/MyServer");          Console.WriteLine("Client.Main(): Creating first object");          MyRemoteObject obj1 = new MyRemoteObject();          obj1.setValue(42);          Console.WriteLine("Client.Main(): Creating second object");          MyRemoteObject obj2 = new MyRemoteObject();          obj2.setValue(4711);          Console.WriteLine("Obj1.getValue(): {0}",obj1.getValue());          Console.WriteLine("Obj2.getValue(): {0}",obj2.getValue());          Console.ReadLine();       }    } } 
end example

When this code sample is run, you will see the same behavior as when using local objects-the two instances have their own state (Figure 3-9). As expected, on the server two different objects are created (Figure 3-10).

click to expand
Figure 3-9: Client-side output when using CAOs

click to expand
Figure 3-10: Server-side output when using CAOs

Using the Factory Design Pattern

From what you've read up to this point, you know that SoapSuds cannot extract the metadata for nondefault constructors. When your application's design relies on this functionality, you can use a factory design pattern, in which you'll include a SAO providing methods that return new instances of the CAO.

Note 

You might also just ship the server-side implementation assembly to the client and reference it directly. But as I stated previously, this is clearly against all distributed application design principles!

Here, I just give you a short introduction to the factory design pattern. Basically you have two classes, one of which is a factory, and the other is the real object you want to use. Due to constraints of the real class, you will not be able to construct it directly, but instead will have to call a method on the factory, which creates a new instance and passes it to the client.

Listing 3-6 shows you a fairly simple implementation of this design pattern.

Listing 3-6: The Factory Design Pattern

start example
 using System; namespace FactoryDesignPattern {    class MyClass    {    }    class MyFactory    {       public MyClass getNewInstance()       {          return new MyClass();       }    }    class MyClient    {       static void Main(string[] args)       {          // creation using "new"          MyClass obj1 = new MyClass();          // creating using a factory          MyFactory fac = new MyFactory();         MyClass obj2 = fac.getNewInstance();       }    } } 
end example

When bringing this pattern to remoting, you have to create a factory that's running as a server-activated object (ideally a Singleton) that has a method returning a new instance of the "real class" (the CAO) to the client. This gives you a huge advantage in that you don't have to distribute the implementation to the client system or manually tweak the output from SoapSuds -gc.

Note 

Distributing the implementation to the client is not only a bad choice due to deployment issues, it also makes it possible for the client user to disassemble your object's codes using ILDASM or some other tool.

You have to design your factory SAO using a shared assembly which contains the interface information (or abstract base classes) which are implemented by your remote objects. This is shown in Listing 3-7.

Listing 3-7: The Shared Interfaces for the Factory Design Pattern

start example
 using System; namespace General {    public interface IRemoteObject    {       void setValue(int newval);       int getValue();    }    public interface IRemoteFactory    {       IRemoteObject getNewInstance();      IRemoteObject getNewInstance(int initvalue);    } } 
end example

On the server you now have to implement both interfaces and create a startup code that registers the factory as a SAO. You don't have to register the CAO in this case because every MarshalByRefObject can be returned by a method call; the framework takes care of the necessity to remote each call itself, as shown in Listing 3-8.

Listing 3-8: The Server-Side Factory Pattern's Implementation

start example
 using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Messaging; using General; namespace Server {    class MyRemoteObject: MarshalByRefObject, IRemoteObject    {       int myvalue;       public MyRemoteObject(int val)       {          Console.WriteLine("MyRemoteObject.ctor(int) called");          myvalue = val;       }       public MyRemoteObject()       {          Console.WriteLine("MyRemoteObject.ctor() called");       }       public void setValue(int newval)       {          Console.WriteLine("MyRemoteObject.setValue(): old {0} new {1}",                                myvalue,newval);          myvalue = newval;       }       public int getValue()       {          Console.WriteLine("MyRemoteObject.getValue(): current {0}",myvalue);          return myvalue;       }    }    class MyRemoteFactory: MarshalByRefObject,IRemoteFactory    {       public MyRemoteFactory() {          Console.WriteLine("MyRemoteFactory.ctor() called");       }       public IRemoteObject getNewInstance()       {          Console.WriteLine("MyRemoteFactory.getNewInstance() called");          return new MyRemoteObject();       }      public IRemoteObject getNewInstance(int initvalue)       {          Console.WriteLine("MyRemoteFactory.getNewInstance(int) called");          return new MyRemoteObject(initvalue);       }    }    class ServerStartup    {       static void Main(string[] args)       {          Console.WriteLine ("ServerStartup.Main(): Server started");          HttpChannel chnl = new HttpChannel(1234);          ChannelServices.RegisterChannel(chnl);          RemotingConfiguration.RegisterWellKnownServiceType(             typeof(MyRemoteFactory),             "factory.soap",             WellKnownObjectMode.Singleton);          // the server will keep running until keypress.          Console.ReadLine();       }    } } 
end example

The client, which is shown in Listing 3-9, works a little bit differently from the previous one as well. It creates a reference to a remote SAO using Activator.GetObject(), upon which it places two calls to getNewInstance() to acquire two different remote CAOs.

Listing 3-9: The Client Uses the Factory Pattern

start example
 using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Remoting.Channels.Tcp; using System.Runtime.Remoting.Channels; using General; namespace Client {    class Client    {       static void Main(string[] args)       {          HttpChannel channel = new HttpChannel();          ChannelServices.RegisterChannel(channel);          Console.WriteLine("Client.Main(): Creating factory");          IRemoteFactory fact = (IRemoteFactory) Activator.GetObject(             typeof(IRemoteFactory),             "http://localhost:1234/factory.soap");          Console.WriteLine("Client.Main(): Acquiring first object from factory");          IRemoteObject obj1 = fact.getNewInstance();          obj1.setValue(42);          Console.WriteLine("Client.Main(): Acquiring second object from " +                             "factory");          IRemoteObject obj2 = fact.getNewInstance(4711);          Console.WriteLine("Obj1.getValue(): {0}",obj1.getValue());          Console.WriteLine("Obj2.getValue(): {0}",obj2.getValue());          Console.ReadLine();       }    } } 
end example

When this sample is running, you see that the client behaves nearly identically to the previous example, but the second object's value has been set using the object's constructor, which is called via the factory (Figure 3-11). On the server a factory object is generated, and each new instance is created using the overloaded getNewInstance() method (Figure 3-12).

click to expand
Figure 3-11: Client-side output when using a factory object

click to expand
Figure 3-12: Server-side output when using a factory object

Managing Lifetime

One point that can lead to a bit of confusion is the way an object's lifetime is managed in the .NET Remoting Framework. Common .NET objects are managed using a garbage collection algorithm that checks if any other object is still using a given instance. If not, the instance will be garbage collected and disposed.

When you apply this schema (or the COM way of reference counting) to remote objects, it pings the client-side proxies to ensure that they are still using the objects and that the application is still running (this is mainly what DCOM did). The reason for this is that normally a client that has been closed unexpectedly or went offline due to a network outage might not have decremented the server-side reference counter. Without some additional measure, these serverside objects would in turn use the server's resources forever. Unfortunately, when your client is behind an HTTP proxy and is accessing your objects using SOAP remoting, the server will not be able to contact the client in any way.

This constraint leads to a new kind of lifetime service: the lease-based object lifetime. Basically this means that each server-side object is associated with a lease upon creation. This lease will have a time-to-live counter (which starts at five minutes by default) that is decremented in certain intervals. In addition to the initial time, a defined amount (two minutes in the default configuration) is added to this time to live upon every method call a client places on the remote object.

When this time reaches zero, the framework looks for any sponsors registered with this lease. A sponsor is an object running on the server itself, the client, or any machine reachable via a network that will take a call from the .NET Remoting Framework asking whether an object's lifetime should be renewed or not (more on this in Chapter 6).

When the sponsor decides that the lease will not be renewed or when the framework is unable to contact any of the registered sponsors, the object is marked as timed out and then garbage collected. When a client still has a reference to a timed-out object and calls a method on it, it will receive an exception.

To change the default lease times, you can override InitializeLifetimeService() in the MarshalByRefObject. In the following example, you see how to change the last CAO sample to implement a different lifetime of only ten milliseconds for this object. Normally LeaseManager only polls all leases every ten seconds, so you have to change this polling interval as well.

 namespace Server {    class MyRemoteObject: MarshalByRefObject, IRemoteObject    {       public override object InitializeLifetimeService()       {          Console.WriteLine("MyRemoteObject.InitializeLifetimeService() called");          ILease lease = (ILease)base.InitializeLifetimeService();         if (lease.CurrentState == LeaseState.Initial)          {             lease.InitialLeaseTime = TimeSpan.FromMilliseconds(10);             lease.SponsorshipTimeout = TimeSpan.FromMilliseconds(10);             lease.RenewOnCallTime = TimeSpan.FromMilliseconds(10);          }          return lease;       }       // rest of implementation ...    }    class MyRemoteFactory: MarshalByRefObject,IRemoteFactory    {       // rest of implementation    }    class ServerStartup    {       static void Main(string[] args)       {          Console.WriteLine ("ServerStartup.Main(): Server started");          LifetimeServices.LeaseManagerPollTime = TimeSpan.FromMilliseconds(10);          HttpChannel chnl = new HttpChannel(1234);          ChannelServices.RegisterChannel(chnl);          RemotingConfiguration.RegisterWellKnownServiceType(             typeof(MyRemoteFactory),             "factory.soap",             WellKnownObjectMode.Singleton);          // the server will keep running until keypress.          Console.ReadLine();       }    } } 

On the client side, you can add a one-second delay between creation and the first call on the remote object to see the effects of the changed lifetime. You also need to provide some code to handle the RemotingException that will get thrown because the object is no longer available at the server. The client is shown in Listing 3-10.

Listing 3-10: A Client That Calls a Timed-Out CAO

start example
 using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels.Http; using System.Runtime.Remoting.Channels.Tcp; using System.Runtime.Remoting.Channels; using General; namespace Client {    class Client    {       static void Main(string[] args)       {          HttpChannel channel = new HttpChannel();          ChannelServices.RegisterChannel(channel);          Console.WriteLine("Client.Main(): Creating factory");          IRemoteFactory fact = (IRemoteFactory) Activator.GetObject(             typeof(IRemoteFactory),             "http://localhost:1234/factory.soap");          Console.WriteLine("Client.Main(): Acquiring object from factory");          IRemoteObject obj1 = fact.getNewInstance();          Console.WriteLine("Client.Main(): Sleeping one second");          System.Threading.Thread.Sleep(1000);          Console.WriteLine("Client.Main(): Setting value");          try          {             obj1.setValue(42);          }          catch (Exception e)          {             Console.WriteLine("Client.Main(). EXCEPTION \n{0}",e.Message);          }          Console.ReadLine();       }    } } 
end example

Running this sample, you see that the client is able to successfully create a factory object and call its getNewInstance() method (Figure 3-13). When calling setValue() on the returned CAO, the client will receive an exception stating the object has timed out. The server runs normally (Figure 3-14).

click to expand
Figure 3-13: The client receives an exception because the object has timed out.

click to expand
Figure 3-14: The server when overriding InitializeLifetimeService()

[1]The only exception from this rule lies in the object's lifetime, which is managed completely differently from the way it is in .NET generally or in COM.




Advanced  .NET Remoting C# Edition
Advanced .NET Remoting (C# Edition)
ISBN: 1590590252
EAN: 2147483647
Year: 2002
Pages: 91
Authors: Ingo Rammer

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net