Message Sinks
Message sinks are one of the most important elements contributing to the extreme flexibility of the .NET Remoting architecture. In Chapter 2, Understanding the .NET Remoting Architecture, we discussed how the .NET Remoting infrastructure connects message sinks together to form message sink chains, which process .NET Remoting messages and move them through contexts and application domains. The .NET Remoting infrastructure uses message sinks in a variety of functional areas, including these:
Contexts, which we ll discuss in the next section of this chapter
Channels, which we ll discuss in Chapter 7, Channels and Channel Sinks
Formatters, which we ll discuss in Chapter 8, Serialization Formatters
IMessageSink
Any type that implements the IMessageSink interface can participate in the .NET Remoting architecture as a message sink. Table 6-1 lists the members of the IMessageSink interface.
Member | Member Type | Description |
NextSink | Read-only property | The next message sink in the chain, or null if this is the last sink in the chain |
AsyncProcessMessage | Method | Processes the message asynchronously |
SyncProcessMessage | Method | Processes the message synchronously |
The following code defines a class that implements IMessageSink:
public class PassThruMessageSink : IMessageSink { ImessageSink _NextSink; public IMessageSink NextSink { get { return _NextSink; } } public PassThruMessageSink( IMessageSink next ) { _NextSink = next; } public virtual IMessage SyncProcessMessage ( IMessage msg ) { try { return _NextSink.SyncProcessMessage(msg); } catch(System.Exception e) { return new ReturnMessage(e, (IMethodCallMessage) msg); } } public virtual IMessageCtrl AsyncProcessMessage( IMessage msg, IMessageSink replySink ) { try { AsyncReplyHelperSink.AsyncReplyHelperSinkDelegate rsd = new AsyncReplyHelperSink.AsyncReplyHelperSinkDelegate( this.AsyncProcessReplyMessage ); replySink = (IMessageSink)new AsyncReplyHelperSink( replySink, rsd ); // Pass message to next sink. return _NextSink.AsyncProcessMessage( msg, replySink ); } catch(System.Exception e) { return null; } } // // When the async call completes, our helper class will call // this method, at which point we can process the return message. public IMessage AsyncProcessReplyMessage( IMessage msg ) { // Process the return message, and return it. return msg; } } // End class PassThruMessageSink
The PassThruMessageSink class is a valid IMessageSink implementation, although it simply passes the IMessage to the next sink in the chain. However, this class does show the general way of implementing IMessageSink, so let s examine the more interesting methods in detail.
Synchronous Message Processing
IMessageSink.SyncProcessMessage looks simple enough. As its name implies, IMessageSink.SyncProcessMessage processes the request message in a synchronous manner by passing that message to the next sink s SyncProcessMessage method. Synchronous processing completes only after the .NET Remoting infrastructure receives and returns the response message from the method call on the remote object, in which case the return value of SyncProcessMessage is an IMessage that encapsulates the return value of the method call and any out parameters.
If an exception occurs during message processing, we wrap the exception in a ReturnMessage, which we return to the caller. This isn t the same thing as an exception occurring during execution of the remote object s method. In that case, the response message returned by the .NET Remoting infrastructure on the server side encapsulates the exception thrown on the server side.
Asynchronous Message Processing
The .NET Remoting infrastructure processes asynchronous method calls by invoking the IMessageSink.AsyncProcessMessage method. Unlike SyncProcessMessage, AsyncProcessMessage doesn t process both the request and response messages. Instead, AsyncProcessMessage processes the request message and returns. Later, when the asynchronous operation completes, the .NET Remoting infrastructure (ironically) passes the response message to the IMessageSink.Sync ProcessMessage method of the sink referenced by the second parameter to the AsyncProcessMessage method, replySink. If you need to process the response message of an asynchronous call, you must add a message sink to the front of the replySink chain prior to passing the request message to the next sink in the chain.
To facilitate implementing AsyncProcessMessage, we ve defined a helper class named AsyncReplyHelperSink that takes a delegate to a callback method that it invokes upon receiving an IMessage in SyncProcessMessage:
// // Generic AsyncReplyHelperSink class - delegates calls to // SyncProcessMessage to delegate instance passed in ctor. public class AsyncReplyHelperSink : IMessageSink { // Define a delegate to the callback method. public delegate IMessage AsyncReplyHelperSinkDelegate(IMessage msg); IMessageSink _NextSink; AsyncReplyHelperSinkDelegate _delegate; public System.Runtime.Remoting.Messaging.IMessageSink NextSink { get { return _NextSink; } } public AsyncReplyHelperSink( IMessageSink next, AsyncReplyHelperSinkDelegate d ) { _NextSink = next; _delegate = d; } public virtual IMessage SyncProcessMessage (IMessage msg ) { if ( _delegate != null ) { // Notify delegate of reply message. The delegate // can modify the message, so save the result and // pass it down the chain. IMessage msg2 = _delegate(msg); return _NextSink.SyncProcessMessage(msg2); } else { return new ReturnMessage( new System.Exception( "AsyncProcessMessage _delegate member is null!"), (IMethodCallMessage)msg ); } } public virtual IMessageCtrl AsyncProcessMessage ( System.Runtime.Remoting.Messaging.IMessage msg , System.Runtime.Remoting.Messaging.IMessageSink replySink ) { // This should not be called in the reply sink chain. The // runtime processes reply messages to asynchronous calls // synchronously. Someone must be trying to use us in a // different chain! return null; } }
The PassThruMessageSink class uses the helper class by instantiating a delegate that targets its AsyncProcessReplyMessage method, passing the delegate to a new instance of the helper class, and adding the instance of the helper class to the reply sink chain. We ll use the AsyncReplyHelperSink class in several examples in the next section.