9.4 The Client

Now that the server is running, it's time to talk about building the client. The million dollar question is how the client will get the type information for ServerInfo . Directly referencing the ServerInfo assembly is one option, but not a realistic one, so it will not be considered . It doesn't take an active imagination to foresee the problems that can occur. Instead, this section examines a few techniques for consuming remote objects that allow the implementation to change without affecting the client.

9.4.1 SoapSuds

The .NET Framework SDK ships with a utility called SoapSuds.exe that can generate a proxy object the client can use. SoapSuds.exe will even generate the source code for the proxy (but only in C#). Just point it to the URL of the remote object, link to the client shown in Example 9-7, and run. The HTTPchannel must be specified along with the port that was given to the server.

The following command creates a proxy object (with C# source) to the Singleton version of ServerInfo :

si looks like a local instance of ServerInfo . However, when the example is run (as shown in Figure 9-3), the statistics for the server machine are returned. The mystery is solved when the source code output from SoapSuds is examined. The namespace, class name , and methods are the same as ServerInfo , but the similarity ends there.

Figure 9-3. Client output

9.4.1.1 Lifetime leases

If the client is run successively, the call count is steadily incremented, proving that the object is in fact a Singleton. However, Singleton objects do not live forever. When created, server-side objects are associated with a lease , which is a countdown to destruction. By default, the lease time is 5 minutes. If a call is not made during this time, the object is garbage collected. Otherwise, when the lease expires , it is renewed, by default, for another 2 minutes.

After running the client, walk away for a while (get a peanut butter and jelly sandwich or something) and come back. When the client is run again, the call count should be back down to 5, indicating that a new instance of the Singleton was activated.

You can change the lease defaults by overriding the InitializeLifetimeService method in the ServerInfo class. To make the Singleton live forever, just return Nothing :

9.4.1.2 Server and client activation

In Example 9-7, ServerInfo is a server-activated object. This means the server is responsible for the lifetime of the object; it determines when the object is no longer used. Each instance of the client will make calls on the same ServerInfo object.

In contrast, you can specify that a remote object be client-activated , meaning the client will have a say in determining an object's lifetime. To illustrate the concept, two changes must be made.

First, the server configuration file must be changed to allow ServerInfo to be client-activated. An <activated> element that supplies the client-activated type and the assembly in which it is located must be added within <service> , as the following code illustrates:

RegisterActivatedType takes the client-activated type and a URL to the type's location. Note the location does not contain the object URI, only the path to the object.

The client-activated object is still created on the server, but the reference held is unique to the client. Running multiple instances of the client shows that the call count returned by the object is 5 for each instance. When the client terminates, the object is garbage collected as if it were local. However, the CAO still has a lease. If the client did not terminate (and if the object is still in scope) and the lease expired , the object would be garbage collected just like an SAO.

9.4.2 Creating an Object Factory

Using SoapSuds to generate proxy assemblies has two shortcomings. First, it doesn't work if the object's constructor takes a parameter (it accepts only default constructors). Second, the server address is hardcoded into the proxy, which makes distribution more difficult. Changes on the server side would require the client to regenerate the proxy.

To get around this problem, you can use an object factory on the server. The idea behind this design pattern is that two interfaces are defined; the first interface (the factory) defines a method that returns the second (the object):

The server configuration file exposes only the ObjectFactory class (which is implemented as a Singleton), preventing clients from establishing a dependency on RemoteObject . New functionality (or implementation of existing methods) can be added to the remote assembly without affecting existing clients.

9.4.2.1 Factory interfaces

Example 9-8 contains the object factory interfaces for a new version of ServerInfo .

9.4.2.2 ServerInfo factory

The ServerInfo class must now be changed in order to implement IServerInfo ; the new version is shown in Example 9-9. In addition to referencing ServerInterfaces.dll , the object factory class must be implemented to return instances of the ServerInfo class.

9.4.3 Using the Object Factory

The Windows Service does not need to be recompiled, because it does not directly reference ServerInfo.dll or ServerInterfaces.dll . However, the configuration file must be changed to expose a well-known Singleton object entry for the factory class (see Example 9-10). Generally, if there are entries for the object type returned by the factory in the config file, they can be removed.

ServerInfo.dll and ServerInterfaces.dll should be moved to the same directory as the service.

There are also several small changes to the client. First, add a reference to ServerInterfaces.dll . Then, remove the reference to ServerInfoProxy.dll .

Getting an interface is the factory's purpose. However, since interfaces cannot be created, another mechanism has to be used to obtain the remote object, which, unfortunately , means no transparent activation (e.g., creating an instance using Dim like a normal object).

Activator.GetObject can be called to return an interface reference to an existing server-side object. This method can be used in place of the old ServerInfo declaration:

The first parameter is the interface typein this case, IServerInfoFactory . The second is the URL of the object as defined in the server config file, including the object URI. The returned Object can be typecast to the factory interface. Note, though, that the proxy does not transmit anything over the network until a method is called. Here, that is during CreateServerInfo .