Bidirectional Communication

The simple .NET Remoting examples developed so far support only one-way communication. That means the client can call methods, retrieve results, and use properties but can't respond to events or callbacks. In short, the server has no way of interacting with the client application except in response to the client.

In many enterprise applications, this simplified approach is ideal. In other cases, however, the server might need some mechanism to notify the client (for example, to signal that a time-consuming asynchronous task is complete or to send a message from another user in a chat application). In this case, a few changes are required.

First, you need to move from the client-server channels used so far to the bidirectional channels identified as "tcp" or "http" in the configuration file. Second, the client application needs to define a port used to listen for server communication. (In the simple client-server scenario, the underlying socket is closed when the communication is complete, so server-initiated interaction isn't possible.) To do this, you just specify a port number in the client configuration file. This can be the same port number (if you're testing on separate machines) or a different port number.

For example, you could define a TCP/IP port in the server configuration file like this:

 <channel ref="tcp" port="8080" /> 

And one in the client configuration file as well:

 <channel ref="tcp" port="8081" /> 

You have one alternative option. You can specify the port number "0" for the client, which instructs it to listen on a port that the CLR selects dynamically at run time. This is the most convenient and most common choice.

 <channel ref="tcp" port="0" /> 

The next hurdle is that the client application needs at least one remotable MarshalByRefObject to receive the notification (which we'll call the event listener). Furthermore, the server application needs a reference to the assembly for this class so that it has enough information to use it just as your client application requires a reference to the remote component assembly.

Assuming you meet these criteria, you have two options. You can define an event in the remote component class and handle the event with your client-side event listener class. Or you can pass a delegate that points to the listener class and allow the remote object to invoke that delegate. (Both of these approaches complicate life; if you need bidirectional communication, I encourage you to pay special attention to the section about interface-based programming in Chapter 11, which provides one way that you can simplify life with a shared component.)

Using Events

This section walks you through the steps needed to add an event to a remote object. Figure 4-8 shows the basic design pattern we'll use.

Figure 4-8. Handling an event with a remotable event listener

graphics/f04dp08.jpg

The first step is to define the event in the remote object and the custom EventArgs object, as shown in Listing 4-17.

Listing 4-17 Creating a remotable custom event
 Public Class RemoteObject     Inherits MarshalByRefObject     Public Event TaskComplete(ByVal sender As Object, _      ByVal e As TaskCompleteEventArgs)     ' (Other class code omitted.) End Class <Serializable()> _ Public Class TaskCompleteEventArgs     Inherits EventArgs     Public Result As Object     Public Sub New(ByVal result As Object)         Me.Result = result     End Sub End Class 

This event follows the .NET standard, passing a reference of the sending object and an EventArgs object with additional information. In this case, the TaskCompleteEventArgs object contains a Result property, which represents the result of a long-running server-side operation. Note that the TaskCompleteEvent­Args object must be marked as serializable so it can be copied across application boundaries.

Next you need to create a remotable client-side component that can receive the event. This component must be implemented as a separate DLL assembly. Why? Both the server and client will need a reference to this object. The server needs the reference in order to be able to fire its event, while the client needs its reference in order to create the listener and receive the event. The .NET Framework does not allow applications to reference an executable assembly, so you need to use an assembly. Incidentally, you can overcome this limitation using the interface-based programming model explored in Chapter 11.

The event listener class simply defines two methods: one designed to receive the event and one that specifies an infinite lifetime, ensuring that the .NET Framework won't prematurely destroy this object (a consideration we'll explore in detail a little later in this chapter). When the event listener receives the event, it can raise another, local event to notify the client form class. In Listing 4-18, however, the event listener just displays a message box with the result.

Listing 4-18 A listener class for a remote event
 Public Class EventListener     Inherits MarshalByRefObject     ' Handles the event.     Public Sub TaskCompleted(ByVal sender As Object, _       ByVal e As RemoteObjects.TaskCompleteEventArgs)         System.Windows.Forms.MessageBox.Show( _          "Event handler received result: " & e.Result.ToString())     End Sub     ' Ensures that this object will not be prematurely released.     Public Overrides Function InitializeLifetimeService() As Object         Return Nothing     End Function End Class 

Here's where things become somewhat tangled. The EventListener component requires a reference to the RemoteObject assembly in order to know how to use the TaskCompleteEventArgs class. In turn, the component host needs to have access to the EventListener assembly so it knows how to communicate with that assembly. To sort out these tangled relationships, refer to Figure 4-9, which shows all four projects in the Visual Studio .NET Solution Explorer, along with their required references.

Figure 4-9. The remote event-handling solution

graphics/f04dp09.jpg

Next you need to add the method in the remote object that fires the event. The idea behind this event pattern is that the client submits a work request and returns to normal life while that work completes asynchronously. So far, we haven't looked at any threading concepts, so we'll implement this asynchronous processing by simply using a simple timer. The timer is enabled when the client calls StartTask. When the timer fires, it disables itself and calls back to the client, as shown in Listing 4-19.

Listing 4-19 Firing the remote event
 Public Class RemoteObject     Inherits MarshalByRefObject     ' (Other class code omitted.)     Private WithEvents tmrCallback As New System.Timers.Timer()     ' Enable the timer.     Public Sub StartTask()         tmrCallback.Interval = 5000         tmrCallback.Start()     End Sub     ' The timer fires. Disable the timer, and fire the event     ' to the client. 
     Private Sub tmrCallback_Elapsed(ByVal sender As System.Object, _      ByVal e As System.Timers.ElapsedEventArgs) _      Handles tmrCallback.Elapsed         tmrCallback.Stop()         ' For this simple test, we hardcode a result of 42.         RaiseEvent TaskComplete(Me, New TaskCompleteEventArgs(42))     End Sub End Class 

The client code is simple enough. It simply creates the EventListener object, hooks it up to the appropriate remote event, and calls the StartTask method. In our forms-based client, this happens in response to a button click, as shown in Listing 4-20.

Listing 4-20 Receiving the remote event
 Public Class ClientForm     Inherits System.Windows.Forms.Form     ' (Designer code omitted.)     ' This is the class that will receive the event.     ' The ClientForm can't handle the event directly, because it isn't     ' a MarshalByRefObject.     Private Listener As New EventListener.EventListener()     Private Sub cmdSubmitRequest_Click(ByVal sender As System.Object, _      ByVal e As System.EventArgs) Handles Button1.Click         ' Hook up the event handler so it triggers the         ' Listener.TaskCompleted() method.         AddHandler RemoteObj.TaskComplete, _                    AddressOf Listener.TaskCompleted         ' Start the remote task.         RemoteObj.StartTask()     End Sub End Class 

After a short delay, the callback fires and the client displays a message indicating that it received the result, as shown in Figure 4-10.

Figure 4-10. Handling an event from a remote component

graphics/f04dp10.jpg

Using Delegates

The event-based approach is a particularly good choice if you're using a singleton object that has several clients because they can all receive the same event. Alternatively, you can register a callback by passing a delegate to the remote object. The remote object can then call the delegate, in much the same way that it fired the event in the preceding example.

To understand delegates, you need to realize that the underlying System.Delegate class is serializable, so it can be copied across application boundaries. However, the delegate contains a MarshalByRefObject reference that points to a remote object using a proxy. Therefore, you can send a copy of a delegate to other components to tell them how to locate a given remote component.

To modify the preceding example for delegate-based event handling, you need to first define a delegate with a suitable signature in the remote class:

 Public Delegate Sub TaskFinishedDelegate(result As Decimal) 

You can use the same event listener class, but you need to modify it to accommodate the new signature, as shown in Listing 4-21.

Listing 4-21 A listener class for a remote callback
 Public Class EventListener     Inherits MarshalByRefObject     ' Handles the event.     Public Sub TaskCompleted(result As Decimal)         System.Windows.Forms.MessageBox.Show( _          "Callback received result: " & result.ToString())     End Sub     ' Ensures that this object will not be prematurely released.     Public Overrides Function InitializeLifetimeService() As Object         Return Nothing     End Function End Class 

The remote object also needs only minor tweaking so that it accepts the delegate and stores it in a suitable member variable, as shown in Listing 4-22.

Listing 4-22 Calling a client method remotely through a delegate
 Public Class RemoteObject     Inherits MarshalByRefObject     ' (Other class code omitted.)     Private WithEvents tmrCallback As New System.Timers.Timer()     Private Callback As TaskCompletedDelegate     Public Sub StartTask(callback as TaskCompletedDelegate)         Me.Callback = callback         tmrCallback.Interval = 5000         tmrCallback.Start()     End Sub     Private Sub tmrCallback_Elapsed(ByVal sender As System.Object, _      ByVal e As System.Timers.ElapsedEventArgs) _      Handles tmrCallback.Elapsed         tmrCallback.Stop()         ' For this simple test, we hardcode a result of 42.         Callback.Invoke(42)     End Sub End Class 

Finally, the client code calls the method and submits a delegate with a reference to the appropriate method, as shown in Listing 4-23.

Listing 4-23 Receiving a remote call through a delegate
 Public Class ClientForm     Inherits System.Windows.Forms.Form     ' (Designer code omitted.)     Private Listener As New EventListener.EventListener()     Private Sub cmdSubmitRequest_Click(ByVal sender As System.Object, _      ByVal e As System.EventArgs) Handles Button1.Click         RemoteObj.StartTask( _          New TaskCompleteDelegate(AddressOf Listener.TaskCompleted)     End Sub End Class 


Microsoft. NET Distributed Applications(c) Integrating XML Web Services and. NET Remoting
MicrosoftВ® .NET Distributed Applications: Integrating XML Web Services and .NET Remoting (Pro-Developer)
ISBN: 0735619336
EAN: 2147483647
Year: 2005
Pages: 174

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