XML Programming Bible
In this section, we will drive home some object remoting concepts by creating some sample applications. We will begin by building a server-activated object and a client-activated object. Next we will explore the concept of distributed events by creating a Producer-Consumer application using the .NET Remoting platform.
Creating Remote Objects
Now let's get down to business and build a remotable object. For our example we will create two different remotable objects. The first will be a server-activated factorial object that computes large factorials on behalf of its clients. The second will be a remote shopping cart component that maintains a shopper's name and a list of items they intend to purchase. We will walk through the steps of designing, coding, and deploying each kind of object.
Factorial—A Server-Activated Remote Object
The factorial object will allow clients to offload the CPU-intensive task of computing factorials for large numbers to a server equipped to do the job. The object will have a single method, computeFactorial(int num), that takes a single integer and returns its factorial.
Now we'll follow the recommended steps to create this remote object.
Step 1. Design your Service
You need to answer these questions to design your remote service.
- What is your host application domain?
- What is your activation model?
- What channel and port will your service use?
- How will clients obtain your service's metadata?
The first question asks how we intend to develop our remotable object. We will use the C# programming language to implement a Windows application for our simple factorial object. Second, our activation model will be server-activated. Moreover, we will make our object a singleton so that clients can share a single instance of it on the server. Third, our application will use tcp port 8081. Finally our client will obtain the service's metadata by linking to a local DLL copy of the service assembly and will programmatically create service instances by calling the Activator.GetObject( ) method.
Step 2. Implement Your Host Application Domain
The code behind our factorial service will be easy to write. The service itself is a mere class that inherits from MarshalByRefObject. Listing 8-16 is a full listing of this class:
Listing 8-16 FactorialService.cs: The guts of our factorial service.
using System; namespace Factorial { public class FactorialService : MarshalByRefObject { public double computeFactorial(int num) { double result = 1; for (int i=2; i <= num; i++) { result *= i; } return result; } } }
This class has no special details other than the fact that it inherits from MarshalByRefObject. This tells .NET that this object should be remoted by marshal-by-reference, instead of marshal-by-value, semantics.
The Listing 8-16 file is compiled into a DLL by executing the following command at the command prompt:
Csc /debug+ /t:library /out:Factorial.dll FactorialService.cs
Don't forget to use the VCVars32.bat script to initialize your command-line environment. This batch file can be found in the Program Files\Microsoft Visual Studio.NET\VC7\bin directory.
The Factorial DLL file will be linked into two other files: DirectHost.cs and FactorialClient.cs. DirectHost.cs will be used on the server side to publish the FactorialService class so that clients can use it. FactorialClient.cs will be the client-side program that looks up the object and invokes it.
Step 3. ConFigure the Remoting System
The remoting system can be configured in one of two ways. The program can import a static config file that contains the class's metadata or it can pass the metadata to the remoting system programmatically. Our example will use the second approach. Here are the lines of code from DirectHost.cs that do that:
WellKnownServiceTypeEntry entry = new WellKnownServiceTypeEntry ("Factorial.FactorialService","Factorial","MyFactorial" ,System.Runtime.Remoting.WellKnownObjectMode.Singleton); RemotingConfiguration.RegisterWellKnownServiceType(entry);
The first line of code creates a WellKnownServiceTypeEntry object, which can be passed directly to the RegisterWellKnownServiceType( ) method that registers the service with the .NET Remoting system. The arguments to the WellKnownServiceTypeEntry are the class name, the assembly name (that is, the name of the DLL file that contains the class), the URI to which the service should be bound, and the mode in which this server-activated object will run. In this case the object is a singleton, which means that a single instance of the object will be shared across all clients.
Step 4. Create and Register the Channel
The channel defines the communications pipe that the server object will listen on while waiting for incoming requests. This includes the protocol and port number. Note that .NET Remoting Services are allowed to share port numbers. As long as their URIs are unique there will be no conflict. Registering the channel requires only a few more lines of code in DirectHost.cs.
System.Runtime.Remoting.Channels.Tcp.TcpServerChannel tcpch = new System.Runtime.Remoting.Channels.Tcp.TcpServerChannel(8081); ChannelServices.RegisterChannel(tcpch);
The application must create an instance of a class that implements the IChannel interface. In this case we are using the TcpServerChannel class the .NET Framework provides. The HttpServerChannel class is also available for use. The TcpServerChannel class uses a binary formatter to serialize and deserialize all communication with the server. This provides better performance than the HttpServerChannel object but with less flexibility. In our previous example we passed the number 8081 into the constructor to indicate the port number that should be used to listen for incoming client requests.
Step 5. Publish Your Class
Once the channel is registered and the remoting system has been configured, our Factorial class is ready to be published for client use. Here is the Listing 8-17 for the DirectHost application:
Listing 8-17 DirectHost.cs: This program registers the FactorialService class with the .NET Remoting framework.
using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; public class DirectHost { public static void Main() { initialize(); Console.WriteLine("DirectHost is ready to process remote messages."); String keyState = ""; while (String.Compare(keyState,"0", true) != 0) { Console.WriteLine("Press a key and ENTER: G=GC.Collect, 0=Exit"); keyState = Console.ReadLine(); Console.WriteLine("Pressed: " + keyState); // Force a GC if (String.Compare(keyState,"G", true) == 0) { Console.WriteLine("GC Collect - start"); GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("GC Collect - done"); } } } public static void initialize() { // register configuration System.Runtime.Remoting.Channels.Tcp.TcpServerChannel tcpch =
new System.Runtime.Remoting.Channels.Tcp.TcpServerChannel(8081); ChannelServices.RegisterChannel(tcpch); WellKnownServiceTypeEntry entry = new WellKnownService TypeEntry("Factorial.FactorialService","Factorial", "MyFactorial",System.Runtime.Remoting.WellKnownObjectMode. Singleton); RemotingConfiguration.RegisterWellKnownServiceType(entry); } }
To compile this class, issue the following command from the command prompt:
csc /debug+ /r:System.Runtime.Remoting.dll DirectHost.cs
All of the .NET Remoting function calls are contained in the initialize( ) function of this program. The program has a main( ) method so that it can be invoked from the command line. This method enters an input loop, waiting for the user to choose an option. The user can choose G to garbage collect the deployed object (which will be reinstantiated the next time a client invokes one of its methods) or 0 to exit. This input loop is necessary so that the program does not exit immediately, destroying the class we just deployed!
If everything is properly compiled your program should produce the output shown in Figure 8-2.
Figure 8-2 The result of running DirectHost.exe to publish the factorial service.
Step 6. Write Your Client
Assuming the DirectHost.exe program is running without a hitch, the FactorialService class is ready for mass consumption. Writing a remote service consumer is slightly easier than writing the producer. The consumer needs to register a TCP channel and then make a call to Activator.GetObject( ), passing in the type of the object it should return and the URI it should use to locate the object. This method will generate a stub class with the same signature as the remote class and will return it to the client for use in its address space. Upon receiving a reference to the remote object, the client is free to use it as it would any ordinary object.
Listing 8-18 is the full listing for our FactorialClient class:
Listing 8-18 FactorialClient.cs: The factorial service consumer.
using System; namespace FactorialClient { public class FactorialClient { public FactorialClient() { } public static void Main(String[] args) { System.Runtime.Remoting.Channels.ChannelServices. RegisterChannel(new System.Runtime.Remoting.Channels.Tcp. TcpChannel()); try { Factorial.FactorialService obj = (Factorial.FactorialService)Activator.GetObject(typeof(Factorial. FactorialService), "tcp://localhost:8081/MyFactorial"); System.Console.Out.WriteLine (obj.computeFactorial(Int32.Parse(args[0]))); } catch(Exception e) { System.Console.Out.WriteLine(e.StackTrace); System.Console.Out.WriteLine(e.Message); } } } }
This class can be compiled by invoking the following command:
csc /debug+ /r:Factorial.dll /out:FactorialClient.exe FactorialClient.cs
The remote method call is extremely easy for the client to make. The computeFactorial( ) method can be invoked on the FactorialService reference without any RPC plumbing code; the .NET takes care of everything for us.
Shopping Cart—A Client-Activated Remote Object
Now we'll look at a more interesting example. Client-activated objects are more powerful than server-activated objects because they allow the remote object to maintain state across method calls. Because each reference to a client-activated object is guaranteed a unique, persistent instance on the server, clients can safely set object state without fear that it will be arbitrarily garbage collected. The object is safe as long as the client maintains its lifetime lease.
Our shopping cart program will maintain a few instance variables, the most important being an ArrayList of items to be purchased. This list can be augmented or trimmed by method calls from the client. Table 8-4 and Table 8-5 show a listing of the shopping cart's members.
Table 8-4 Shopping Cart Private Instance Properties
| Property | Description |
|---|---|
String firstName | The shopper's first name. |
String lastName | The shopper's last name. |
ArrayList items | A list of items to be purchased. |
Table 8-5 Shopping Cart Public Instance Methods
| Method | Description |
|---|---|
void setName( ) | Sets the shopper's full name. |
String getName(String firstName, String lastName) | Returns the shopper's full name. |
void addItem(Object item) | Adds a new item to the shopping cart's contents. |
void removeItem(int itemIndex) | Removes the item at the specified index from the shopping cart. |
String getCartContents( ) | Returns a string describing the contents of the shopping cart. |
Step 1. Design Your Service
Our shopping cart service will differ from the Factorial service in a few key areas. First the service will be client-activated, meaning that clients will control the object instances' life cycles. Second we will use the IIS to handle the deployment of the object. This will eliminate the need to write a DirectHost.cs program that houses the shared object. IIS will take care of registering the object in the .NET Remoting framework as long as we supply its metadata in a config file. Deploying the object through the IIS means that clients will be restricted to communicating with the object through only the HTTP channel. Just like before, our remote class will be written using the C# programming language.
Step 2. Implement Your Host Application Domain
All we need to do here is write our class and be sure to extend MarshalByRefObject. Listing 8-19 is the class listing.
Listing 8-19 ShoppingCart.cs: This object is client-activated and doesn't even know it!
using System; using System.Runtime.Remoting; using System.Collections; namespace ShoppingCart { // ShoppingCart is a client-activated object that // * Is exported in the remoting configuration file // * Has a Constructor with parameters // * Has public Properties that can set and get // * Has fields in which state is stored between calls // * Has methods that can be called to change the state // * Has overloaded methods public class ShoppingCart : MarshalByRefObject { public String firstName = ""; public String lastName = ""; private ArrayList cart = null; public ShoppingCart(String _firstName, String _lastName) { firstName = _firstName; lastName = _lastName; } // public properties public String FirstName { get { Console.WriteLine("First Name: {0}", firstName); return firstName; } set { lock(this) { firstName = value; Console.WriteLine("First Name: {0}", firstName); } } } public String LastName { get { Console.WriteLine("Last Name: {0}", LastName); return lastName; } set { lock(this) { lastName = value; Console.WriteLine("Last Name: {0}", LastName); } } } public void setName(String _firstName, String _lastName) { lock(this) { firstName = _firstName; lastName = _lastName; } } public void addItem(Object item) { lock(this) { if (cart == null) cart = new ArrayList(); cart.Add(item); } } public void removeItem(int index) { if (cart != null && index < cart.Count) { lock(this) { cart.RemoveAt(index); } } } public String getCartContents() { String contents = firstName+" "+lastName+"'s shopping cart conents:\n"; for (int i = 0; i < cart.Count; i++) { contents += cart[i]+"\n"; } return contents; } } }
This class can be compiled with the following command line:
csc /debug+ /t:library /out:ShoppingCart.dll ShoppingCart.cs
As you can see, nothing in this class definition indicates that the object will be client-activated (or even shared at all!). Unlike server-activated classes, client-activated classes don't have any restrictions on what they can do. They can maintain state, have specialized constructors, and expose public properties to be retrieved or updated.
Step 3. ConFigure the Remoting System
In this example we need to conFigure the IIS manually to deploy the object. Follow these steps to conFigure the IIS:
- Compile ShoppingCart.cs into a DLL file. Copy this file into the bin sub-directory under the ShoppingCart main directory.
- Start the Internet Services Manager and highlight the Default Web Site under the server node.
- Select Action \ New \ Virtual Directory on the menu and click Next.
- Enter ShoppingCart as the virtual directory's alias and click Next.
- Enter the full path up to but excluding the bin directory under the ShoppingCart main directory.
- Create a Web.config file (Listing 8-20) in the directory where the service was registered. This file includes the service's metadata and is automatically loaded by the IIS whenever a client attempts to access the object.
Listing 8-20 Web.config: This file contains the server-side metadata for our remote object. The file is loaded by the IIS.
<configuration> <system.runtime.remoting> <application> <service> <activated type="ShoppingCart.ShoppingCart, ShoppingCart"/> </service> </application> </system.runtime.remoting> </configuration>
Step 4. Create and Register the Channel
This step is handled by the IIS.
Step 5. Publish Your Class
This step is handled by the IIS.
Step 6. Write Your Client
We are able to write a much richer client than we were able to with the previous example. The ShoppingCart client will create separate ShoppingCart instances, each with different state values. The client will alter the objects' state by calling their methods. The client will lastly attempt to print out each object's state by calling the getCartContents( ) methods and sending the result to the console, which is shown in Listing 8-21.
Listing 8-21 Client.cs: This client program creates many instances of the client-activated shopping cart objects.
using System; using System.IO; using System.Text; using System.Runtime.Remoting; using System.Security.Policy; using System.Threading; public class Client { public static int Main(string[] args) { // Load the Http Channel from the config file RemotingConfiguration.Configure("Client.exe.config"); ShoppingCart.ShoppingCart cart1 = new ShoppingCart. ShoppingCart("Bob", "Smith"); cart1.setName("Bob","Jones"); cart1.firstName = "Robert"; cart1.addItem("Carrots"); cart1.addItem("Peas"); cart1.addItem("String Beans"); cart1.addItem("Apples"); ShoppingCart.ShoppingCart cart2 = new ShoppingCart. ShoppingCart("Jane","Doe"); cart2.lastName = "Dough"; cart2.addItem("Porsche 911 Cabrio"); cart2.addItem("Cartier sunglasses"); cart2.addItem("Leather driving gloves"); cart2.addItem("Map of Blue Ridge Parkway"); ShoppingCart.ShoppingCart cart3 = new ShoppingCart. ShoppingCart("Steve", "Jones"); cart3.firstName = "Ron"; cart3.lastName = "Jones"; cart3.addItem("Microsoft Office XP"); cart3.addItem("Microsoft .NET Developer Studio"); cart3.addItem("Sun Solaris"); cart3.removeItem(2); cart3.addItem("Microsoft Windows 2000 Server"); System.Console.Out.WriteLine(cart1.getCartContents()); System.Console.Out.WriteLine(cart2.getCartContents()); System.Console.Out.WriteLine(cart3.getCartContents()); return 0; } }
To compile this program, copy the ShoppingCart DLL file into the client directory and type:
csc /debug+ /r:System.Runtime.Remoting.dll /r:ShoppingCart.dll
/out:client.exe client.cs
This client program only contains a single line of code, which is obviously related to .NET Remoting:
RemotingConfiguration.Configure("Client.exe.config");
This line loads a configuration file from the file system that contains the remoted object's metadata that the client needs to locate the class and create an instance of it. The contents of this file are displayed in Listing 8-22.
Listing 8-22 Client.exe.config: An XML configuration file describing the ShoppingCart client.
<configuration> <system.runtime.remoting> <application name="Client"> <client url="HTTP://localhost/ShoppingCart"> <activated type="ShoppingCart.ShoppingCart, ShoppingCart" /> </client> <channels> <channel type="System.Runtime.Remoting. Channels.Http.HttpChannel, System.Runtime.Remoting" /> </channels> </application> </system.runtime.remoting> </configuration> If everything compiles and deploys correctly, the client should produce the following output: Robert Jones's shopping cart contents: Carrots Peas String Beans Apples Jane Dough's shopping cart contents: Porsche 911 Cabrio Cartier sunglasses Leather driving gloves Map of Blue Ridge Parkway Ron Jones's shopping cart contents: Microsoft Office XP Microsoft .NET Developer Studio Microsoft Windows 2000 Server
Remoting Events
In addition to distributed objects, the .NET Remoting framework supports distributed events. Distributed events are extremely useful for implementing the producer-consumer event paradigm in a networked environment. The producer-consumer design pattern dictates a loose coupling between the event producer and the event consumers. Consumers should be able to register and unregister themselves with no impact on the Producer. The producer does not even need to know how many (if any) consumers are interested in what it has to say.
This pattern can be used to solve many problems. One compelling use of the pattern is to avoid busy-waiting, or polling. An event consumer can register a callback function to be executed whenever a specific event occurs. The consumer can do something else, knowing that its event handler will be called at the appropriate time. The producer-consumer pattern is also frequently used in building UIs. However, most UI events involve producers and consumers in the same application domain.
In this section we will explore a distributed event architecture in which the producers and consumers can reside in different application domains or even different machines. The files used in this example can be found on the .NET Framework SDK. In the example we will pass a "greeting" event from producer to consumer to convey a simple message.
The Event Class
The Greeting event class will define the actual event object to be passed from producer to consumers. The event arguments object must be serializable so it can be passed by value into each of the registered consumers. This is accomplished by associating the SerializableAttribute with the event class. The GreetingEventArgs class will contain a single instance variable called greeting that will contain the salutation text.
Our class file will also contain a remotable service called "Waz" that listens for incoming greetings. The Waz service will be a server-activated Singleton class. Clients can call Zap's HelloMethod( ) method, passing in a greeting that they would like to announce. The Waz service will create an instance of the GreetingEvent class and will publish it to all the consumers. Listing 8-23 shows the zap.cs source file, which contains the event logic for the distributed events program.
Listing 8-23 Zap.cs: this file contains the event, eventArgs, and publishingService code for our distributed event system.
/*================================================================== File: Zap.cs This file is part of the Microsoft .NET Framework SDK Code Samples. Copyright (C) Microsoft Corporation. All rights reserved. This source code is intended only as a supplement to Microsoft Development Tools and/or online documentation. See these other materials for detailed information regarding Microsoft code samples. THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE. ==================================================================*/ using System; using System.Runtime.Remoting; using System.Collections; namespace Zap { // Define the event arguments [Serializable] public class GreetingEventArgs : EventArgs { public GreetingEventArgs(string greeting) { this.greeting = greeting; } public string greeting; } // Define the event public delegate void GreetingEvent (object sender, GreetingEventArgs e); // Define the Service public class Waz : MarshalByRefObject { // The client will subscribe and // unsubscribe to this event public event GreetingEvent Greeting; // Method called remotely by client public void HelloMethod(string greeting) { Console.WriteLine("Received String {0}", greeting); // Package String in GreetingEventArgs GreetingEventArgs e = new GreetingEventArgs(greeting); // Fire Event if (Greeting != null) { Console.WriteLine("Firing Event"); Greeting(this, e); } } } }
Notice the serializable attribute associated with the GreetingEventArgs class as well as the MarshalByRefObject superclass attached to the Waz service. This means that GreetingEventArgs will be passed by value into all the consumer application domains while Waz instances will be referenced remotely by clients.
The EventFireHost Service Application
We defined the Waz service class in the Zap.cs file. Now we need a console application to register the channel, register the service, and deploy the service through the remoting framework. The Host class in Listing 8-24, EventFireHost.cs, contains a Main( ) method that does this. The class performs these tasks programmatically instead of making use of a configuration file. Upon registering and deploying the service, the application sits and waits for a user to tell it to terminate.
Listing 8-24 EventFireHost.cs: The server-side service application.
/*================================================================== File: EventFireHost.cs This file is part of the Microsoft .NET Framework SDK Code Samples. Copyright (C) Microsoft Corporation. All rights reserved. This source code is intended only as a supplement to Microsoft Development Tools and/or online documentation. See these other materials for detailed information regarding Microsoft code samples. THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE. ==================================================================*/ using System; using System.IO; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; public class Host { public static void Main(string[] args) { // Manually load the http channel. // This could also be done in the remoting configuration file. ChannelServices.RegisterChannel(new HttpChannel(999)); // Register the wellknown server type. // This could also done in the remoting configuration file. RemotingConfiguration.RegisterWellKnownServiceType( Type.GetType("Zap.Waz, Zap"), "EventFireHost/Waz.soap", WellKnownObjectMode.Singleton); // We are done, wait until the user wants to exit Console.WriteLine("Host is ready to process remote messages."); Console.WriteLine("Press ENTER to exit"); String keyState = Console.ReadLine(); } }
Once the EventFireHost service application is up and running, clients can begin subscribing to events, producing events, or both via the Waz object.
The Event Listener
We will now define a new class that creates a GreetingEventHandler callback method. This class can be included by any client application that wants to register itself as a GreetingEvent listener.
Our event handler class, Baz, will inherit from MarshalByRefObject. This will enable our client application to register a stub, or proxy, for this class to be called whenever the event fires. An instance of GreetingEventArgs will be passed by value into the instance of the Baz class residing in the client's application domain. The Baz event handler in Listing 8-25 will print the greeting to the client's console.
Listing 8-25 Wak.cs: This file contains the Baz GreetingEvent handler class.
/*================================================================== File: Wak.cs-------------------------------------------------- This file is part of the Microsoft .NET Framework SDK Code Samples. Copyright (C) Microsoft Corporation. All rights reserved. This source code is intended only as a supplement to Microsoft Development Tools and/or online documentation. See these other materials for detailed information regarding Microsoft code samples. THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE. ==================================================================*/ using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Messaging; using Zap; namespace Wak { // Marshal by Ref Object onto which the event will be fired public class Baz : MarshalByRefObject { [OneWay] public void GreetingHandler(object sender, GreetingEventArgs e) { Console.WriteLine("GreetingHandler callback : Greeting : {0}\n", e.greeting); } } }
The OneWay attribute indicates that the calling program has no way to find out the results of the method call. It does not return any values, nor does it set any properties in the client stub that would indicate successful completion. As such, the .NET Remoting framework can choose to invoke this method asynchronously because the calling program does not need to wait for it to return.
Let's move on to the event handler client program that creates an instance of the Baz object. The Client class in Listing 8-26, EventSinkHost.cs, instantiates the GreetingHandler and registers it as a WellKnown Service. It might look strange that a client program is registering a remotable object as a singleton service. The EventHandler client is special in that it needs to act as a service so that the event dispatcher can call into it. When this happens, the event producer serializes the GreetingEventArgs object and passes it by value into the GreetingHandler( ) method.
Listing 8-26 EventSinkHost.cs: This client program registers the GreetingEventHandler as a WellKnownService.
/*================================================================== File: EventSinkHost.cs This file is part of the Microsoft .NET Framework SDK Code Samples. Copyright (C) Microsoft Corporation. All rights reserved. This source code is intended only as a supplement to Microsoft Development Tools and/or online documentation. See these other materials for detailed information regarding Microsoft code samples. THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE. ==================================================================*/ using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; using Zap; using Wak; public class Client { public static void Main(String[] args) { // This could also be done with a Remoting configuration file // Register the HTTP Channel ChannelServices.RegisterChannel(new HttpChannel(888)); // Register the wellknown server type. // This could also done in the remoting configuration file. RemotingConfiguration.RegisterWellKnownServiceType( Type.GetType("Wak.Baz, Wak"), "EventSinkHost/Baz.soap", WellKnownObjectMode.Singleton); // We are done, wait until the user wants to exit Console.WriteLine("Host is ready to process remote messages."); Console.WriteLine("Press ENTER to exit"); String keyState = Console.ReadLine(); } }
The URI given to the event handler service is EventSinkHost/Baz.soap. The event producer must know this URI so it can notify the program of the events when they occur. When executed, this program sits still, listening for incoming event notices.
Now we will move on to the event service clients and see what they do.
Event Clients
The first client class we will look at is relatively simple. It uses the .NET Remoting framework Activator to create a remote reference to the Waz Web Service class. Upon obtaining this reference the client invokes the remote HelloMethod( ) method five times. Listing 8-27 shows a client to connect to a service.
Listing 8-27 Client.cs: A simple client to connect to the EventFireHost service.
/*================================================================== File: Client.cs This file is part of the Microsoft .NET Framework SDK Code Samples. Copyright (C) Microsoft Corporation. All rights reserved. This source code is intended only as a supplement to Microsoft Development Tools and/or online documentation. See these other materials for detailed information regarding Microsoft code samples. THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE. ==================================================================*/ using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; using Zap; public class Client { public static void Main(String[] args) { // This could also be done with a Remoting configuration file // Register the HTTP Channel ChannelServices.RegisterChannel(new HttpChannel()); // Obtain a Proxy to the SOAP URL Waz waz = (Waz)Activator.GetObject( typeof(Waz), "http://localhost:999/EventFireHost/Waz.soap" ); for (int i = 0; i < 5; i++) { // Occurs over SOAP to waz) waz.HelloMethod("Bill" + " " + i); } } }
You can see the EventFireHost executable print out information about these methods as they occur. However, the EventSinkHost event listener executable remains silent. What happened?
Notice that the Waz service class only fires the event if its event property is not null.
// Fire Event if (Greeting != null) { Console.WriteLine("Firing Event"); Greeting(this, e); }
The service client is responsible for creating a remote reference to the GreetingEventHandler and set the event property. Therefore, event-producing clients need to create two remote references: one to call the Waz service and the other to obtain a reference to the event handler to be passed into the GreetingEvent constructor method. The modified client class file, Listing 8-28, follows.
Listing 8-28 EventRegistration.cs: This client calls the EventFireHost service and kicks off a distributed event to boot.
/*================================================================== File: EventRegistration.cs---- This file is part of the Microsoft .NET Framework SDK Code Samples. Copyright (C) Microsoft Corporation. All rights reserved. This source code is intended only as a supplement to Microsoft Development Tools and/or online documentation. See these other materials for detailed information regarding Microsoft code samples. THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE. ==================================================================*/ using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; using Zap; using Wak; public class Client { public static void Main(String[] args) { // This could also be done with a Remoting configuration file // Register the HTTP Channel ChannelServices.RegisterChannel(new HttpChannel(0)); Baz baz = (Baz)Activator.GetObject( typeof(Baz), "http://localhost:888/EventSinkHost/Baz.soap" ); // Obtain a Proxy to the SOAP URL Waz waz = (Waz)Activator.GetObject( typeof(Waz), "http://localhost:999/EventFireHost/Waz.soap" ); // Subscribe to event : occurs over SOAP waz.Greeting += new GreetingEvent(baz.GreetingHandler); waz.HelloMethod("Hi from the client"); // Unsubscribe to event : occurs over SOAP waz.Greeting -=new GreetingEvent(baz.GreetingHandler); } }
Notice that this client makes an additional call to Activator.GetObject( ) to create a remote reference to the Baz GreetingEventHandler class. The reference to this handler is added to the event dispatcher in the line:
waz.Greeting += new GreetingEvent(baz.GreetingHandler);
This time, through, the greeting property on the Waz object is no longer null. The Waz object reports that it is about to fire the event and notifies the listener by issuing the following statement.
Greeting(this, e);
If everything is running properly you should see the EventSinkHost client print out the line: GreetingHandler callback: Greeting :Hi from the client. Remotable events are extremely powerful, if somewhat difficult to understand at first. We can extend our Event listener client to register itself on the service's event queue and remove itself once it becomes sick of receiving messages.