Creating a Formatter Sink
As discussed in Chapter 2, .NET Remoting uses formatter sinks to format IMessage objects to a stream that s then passed to the remaining channel sinks in the channel for eventual delivery to the remote object. The .NET Remoting infrastructure separates formatter sink functionality into two types: client formatter sinks and server formatter sinks.
Client Formatter Sink
The first sink in the client-side channel sink chain is an instance of a client formatter sink that implements the IClientFormatterSink interface. The client formatter sink acts as a bridge between the message sink chain and the channel sink chain. As such, the client formatter sink is both a message sink and a channel sink. The IClientFormatterSink interface is a composite of the IMessageSink, IClientChannelSink, and IChannelSinkBase interfaces. The following code listing defines a class named MyClientFormatterSink that uses the custom formatter, MyFormatter, we developed in the previous section:
public class MyClientFormatterSink : IClientFormatterSink { private IClientChannelSink _NextChannelSink; private IMessageSink _NextMessageSink; public MyClientFormatterSink(IClientChannelSink next) { _NextChannelSink = next; } // // IChannelSinkBase public IDictionary Properties { get{ return null; } } // // IClientChannelSink public IClientChannelSink NextChannelSink { get{return _NextChannelSink;} } public void AsyncProcessRequest( IClientChannelSinkStack sinkStack, IMessage msg, ITransportHeaders headers, Stream stream ) { // This sink must be first in the chain, so this // method should never be called. throw new NotSupportedException(); } public void AsyncProcessResponse ( IClientResponseChannelSinkStack sinkStack, object state, ITransportHeaders headers, Stream stream ) { // Could implement, but have not. throw new NotImplementedException(); } public System.IO.Stream GetRequestStream ( IMessage msg, ITransportHeaders headers ) { // This sink must be first in the chain, so this // method should never be called. throw new NotSupportedException(); } public void ProcessMessage ( IMessage msg, ITransportHeaders requestHeaders, Stream requestStream, out ITransportHeaders responseHeaders, out Stream responseStream ) { // This sink must be first in the chain, so this // method should never be called. throw new NotSupportedException(); } // // IMessageSink public System.Runtime.Remoting.Messaging.IMessageSink NextSink { get{return _NextMessageSink;} } public IMessageCtrl AsyncProcessMessage (IMessage msg, IMessageSink replySink) { // Could implement, but have not. throw new NotImplementedException(); } public IMessage SyncProcessMessage ( IMessage msg ) { // // Serialize message to a stream. TransportHeaders requestHeaders = new TransportHeaders(); Stream requestStream = _NextChannelSink.GetRequestStream(msg, requestHeaders); if ( requestStream == null ) { requestStream = new System.IO.MemoryStream(); } RemotingSurrogateSelector rem_ss = new RemotingSurrogateSelector(); MyFormatter fm = new MyFormatter(); fm.SurrogateSelector = rem_ss; fm.Context = new StreamingContext( StreamingContextStates.Other ); // Serialize a MethodCall message to the stream. MethodCall mc = new MethodCall(msg); fm.Serialize(requestStream, mc); // // Let sink chain process the message. ITransportHeaders responseHeaders = null; System.IO.Stream responseStream = new System.IO.MemoryStream(); this._NextChannelSink.ProcessMessage( mc, requestHeaders, requestStream, out responseHeaders, out responseStream ); // Use our version of IMessage to deserialize. fm.SurrogateSelector = null; // The formatter handles IMessage types as a // special case and deserializes them as MyMessage types. MyMessage mr = (MyMessage)fm.Deserialize(responseStream); return mr.ConvertMyMessagePropertiesToMethodResponse(mc); } }
First, we d like to make a few remarks about the MyClientFormatterSink class implementation. Because the client formatter sink is the first sink in the channel sink chain, we don t expect the following IClientChannelSink methods to be called: AsyncProcessRequest, GetRequestStream, and ProcessMessage. Thus, each of these IClientChannelSink methods returns a NotSupportedException exception. We also haven t implemented the functionality to support asynchronous calls and therefore return a NotImplementedException exception from the IClientChannelSink.AsyncProcessResponse and IMessageSink.AsyncProcessMessage methods, the implementation of which we ll leave as an exercise for you.
The real work occurs in the IMessage.SyncProcessMessage method. In general, a client formatter sink s implementation of SyncProcessMessage should perform the following tasks:
Obtain a request stream for serializing the request message.
Serialize the message to the request stream.
Pass the request stream to the ProcessMessage method on the next channel sink in the chain.
Deserialize the return message from the response stream and return it.
The MyClientFormatterSink implementation of SyncProcessMessage creates a new instance of the TransportHeaders class, which we then use along with the msg parameter to obtain a request stream by calling the GetRequestStream method on the next sink in the channel sink chain. If GetRequestStream doesn t return a Stream instance, we create a memory stream.
Because we ll be serializing .NET Remoting infrastructure types, we configure the formatter instance with an instance of the RemotingSurrogateSelector class. Also notice that we serialize a new instance of a MethodCall message to the stream rather than the IMessage instance passed to SyncProcessMessage. The client formatter sink s SyncProcessMessage method receives a System.Runtime.Remoting.Messaging.Message instance in its msg parameter. The Message class implements ISerializable but doesn t implement the special constructor needed for deserialization. The MessageSurrogate handles serialization for the Message type but doesn t support deserialization of any IMessage types. Instead of serializing the Message type to the stream, we can create a new instance of the MethodCall class passing the msg instance to the constructor. Unlike the Message class, the MethodCall class implements ISerializable and implements the special constructor needed for deserialization. Once the new instance of MethodCall is in hand, we serialize it to the stream referenced by the requestStream variable, which we then pass to the next sink in the channel sink chain by calling the ProcessMessage method on the _NextChannelSink member. After the call to the next channel sink s ProcessMessage returns, we set the formatter s SurrogateSelector property to null and deserialize the response stream to the MyMessage type. (See the sidebar, Why the MyMessage Class? ) Finally, we convert the MyMessage type to a MethodResponse instance, which we then return.
Why the MyMessage Class?
While implementing the formatter sinks, we encountered a problem during deserialization of MethodCall instances. In the server formatter sink, after deserializing the MethodCall instance, we attempted to pass the instance to the next channel sink s ProcessMessage method. This resulted in the throwing of a StackOverflowException exception.The only way we could get around this problem was by implementing a class named MyMessage, which the formatter creates in place of IMessage types during deserialization. We had to modify the MyFormatter.ReadObjectMembers method so that immediately after creating an uninitialized object instance, the method checks the object s type to determine whether it implements the IMessage interface. If the object s type does implement this interface, we create an instance of the MyMessage class in place of the uninitialized object instance created by FormatterServices.GetUninitializedObject.
Therefore, instead of a MethodCall instance occurring in the deserialized object graph, the MyFormatter class creates a MyMessage instance. The MyMessage class implements the IMessage interface and basically acts as a temporary placeholder, storing the message properties for instances of types that implement the IMessage interface.
The MyMessage class provides two methods, ConvertMyMessagePropertiesToMethodCall and ConvertMyMessagePropertiesToMethodResponse, which convert any message properties that are instances of MyMessage to an instance of MethodCall or MethodResponse, as appropriate.
ClientFormatterSinkProvider
Now that we have a client formatter sink, we need a channel sink provider class that we can use to install the formatter sink into the client channel sink chain. The following code listing defines the MyFormatterClientSinkProvider class:
public class MyFormatterClientSinkProvider : IClientFormatterSinkProvider { public MyFormatterClientSinkProvider() { } public MyFormatterClientSinkProvider( IDictionary properties, ICollection providerData) { } // Build the client-side channel sink chain: public IClientChannelSink CreateSink ( IChannelSender channel, string url , object remoteChannelData ) { // Ask the next provider for the sink chain. IClientChannelSink chain = _Next.CreateSink(channel,url,remoteChannelData); // Add our formatter to the beginning of the chain. IClientChannelSink sinkFormatter = new MyClientFormatterSink(chain); return sinkFormatter; } private IClientChannelSinkProvider _Next=null; public IClientChannelSinkProvider Next { get{return _Next;} set{_Next = value;} } }
Server Formatter Sink
Unlike the client formatter sink, a server formatter sink isn t both a message sink and a channel sink; it s only a channel sink. Server formatter sinks implement the IServerChannelSink interface and are the last sink in the channel sink chain. The following code listing defines a class named MyServerFormatterSink that uses the MyFormatter custom formatter that we developed earlier in the chapter:
public class MyServerFormatterSink : IServerChannelSink { private IServerChannelSink _NextChannelSink; public MyServerFormatterSink( IServerChannelSink snk) { _NextChannelSink = snk; } public IDictionary Properties { get { return null; } } public IServerChannelSink NextChannelSink { get { return _NextChannelSink; } } public Stream GetResponseStream( IServerResponseChannelSinkStack sinkStack, object state, IMessage msg, ITransportHeaders headers) { // We don't expect this method to be called because // we don't push ourselves onto the response sink stack. throw new NotSupportedException(); } public void AsyncProcessResponse( IServerResponseChannelSinkStack sinkStack, object state, IMessage msg, ITransportHeaders headers, Stream stream) { // Could implement, but have not. throw new NotImplementedException(); } public ServerProcessing ProcessMessage( IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, System.IO.Stream requestStream, out IMessage responseMsg, out ITransportHeaders responseHeaders, out System.IO.Stream responseStream) { // Initialize output parameters. responseMsg = null; responseHeaders = null; responseStream = null; // Set up to deserialize request stream. RemotingSurrogateSelector rem_ss = new RemotingSurrogateSelector(); MyFormatter fm = new MyFormatter(); fm.SurrogateSelector = null; fm.Context = new StreamingContext( StreamingContextStates.Other ); IMessage msg = null; MyMessage mymsg = (MyMessage)fm.Deserialize(requestStream); // // Massage the URI property. string uri = (string)mymsg.Properties["__Uri"]; int n = uri.LastIndexOf("/"); if ( n != -1 ) { uri = uri.Substring(n); mymsg.Properties["__Uri"] = uri; } // Convert from MyMessage to MethodCall. MethodCall mc = mymsg.ConvertMyMessagePropertiesToMethodCall(); msg = (IMessage)mc; // When calling the dispatch sink (the next sink in our // chain), the request stream must be null. ServerProcessing sp = this._NextChannelSink.ProcessMessage( sinkStack, msg, requestHeaders, null, out responseMsg, out responseHeaders, out responseStream ); if ( sp == ServerProcessing.Complete ) { // Serialize response message to the response stream. if ( responseMsg != null && responseStream == null ) { responseStream = sinkStack.GetResponseStream( responseMsg, responseHeaders); if ( responseStream == null ) { responseStream = new MemoryStream(); } fm.SurrogateSelector = rem_ss; fm.Serialize(responseStream, responseMsg); } } return sp; } }
The real work occurs in the IServerChannelSink.ProcessMessage method. In general, a server formatter sink s implementation of ProcessMessage should perform the following tasks:
Deserialize a MethodCall instance from the request stream.
Pass the MethodCall instance to the ProcessMessage method of the next sink in the chain, the DispatchSink.
Obtain a response stream for serializing the response message.
Serialize the response message into the response stream.
The MyServerFormatterSink implementation of ProcessMessage first initializes its output parameters, creates an instance of the MyFormatter class, and gets the formatter ready to deserialize the IMessage from the request stream. Because of the problems discussed in the Why the MyMessage Class? sidebar, we deserialize the message object as an instance of the MyMessage class. Next we need to convert the __Uri property of the message to an objectUri. To do so, we keep only the part of the __Uri property string value that follows the last / . If we don t modify the __Uri property value, we ll get an exception similar to the one shown in Figure 8-4 when we try to dispatch the message.
Figure 8-4. Exception resulting from not modifying the __Uri property prior to passing to the DispatchSink
IMPORTANT
The .NET Remoting infrastructure requires that the message object passed to the DispatchSink be a .NET Framework defined IMessage implementing type. Originally, we were passing the MyMessage type, which implements IMessage, but this caused the runtime to throw an exception stating, Permission denied. Cannot call methods on AppDomain class remotely.
After modifying the __Uri property, we convert the MyMessage instance to a MethodCall instance, which we then pass to the ProcessMessage method on the next channel sink, the DispatchSink. If the return value of the ProcessMessage indicates that the method call is complete, we obtain a stream for serializing the response message by first calling the GetRequestStream method on the IServerChannelSinkStack object. This is the standard convention for obtaining a stream and allows sinks in the sink chain to add information to the response stream prior to the server formatter sink serializing the response message. If GetRequestStream doesn t return a Stream instance, the server formatter sink creates one. We then set the formatter s SurrogateSelector property to an instance of the RemotingSurrogateSelector class and serialize the response message to the response stream.
ServerFormatterSinkProvider
Now that we have a server formatter sink, we need a channel sink provider class that we can use to install the formatter sink into the server channel sink chain. The following code listing defines the MyFormatterServerSinkProvider class:
public class MyFormatterServerSinkProvider : IServerFormatterSinkProvider { public MyFormatterServerSinkProvider() { } // This ctor form provides properties from configuration file. public MyFormatterServerSinkProvider( IDictionary properties, ICollection providerData ) { } private IServerChannelSinkProvider _Next; public IServerChannelSink CreateSink(IChannelReceiver channel) { IServerChannelSink chain = _Next.CreateSink(channel); IServerChannelSink sinkFormatter = new MyServerFormatterSink(chain); return sinkFormatter; } public void GetChannelData(IChannelDataStore channelData) { if ( _Next != null ) { _Next.GetChannelData(channelData); } } public IServerChannelSinkProvider Next { get { return _Next; } set { _Next = value; } } }