Microsoft .NET Remoting (Pro-Developer) - page 32

Summary

This completes our discussion of messages and proxies. After examining messages, we used a variety of message types in our custom proxy examples and message sinks. Because messages are the fundamental unit of data transfer in .NET Remoting applications, we’ll use them extensively in Chapters 6, 7, and 8 of this book. Next we discussed custom proxies and their uses as a client-centric part of the .NET Remoting infrastructure. Understanding proxies is important because they generate messages and are the first layer exposed to the client code. We discussed three classes that comprise the .NET Remoting proxy layer and showed how you can create your own proxy to intercept activation and general method calls. Because proxies are the first customizable object in the .NET Remoting infrastructure, they’re ideal for intercepting the activation process and controlling the dispatching of remote method calls. In Chapter 6, we’ll discuss contexts in depth, after we cover message sinks.

Chapter 6

Message Sinks and Contexts

In this chapter, we’ll continue exploring the customization of the .NET Remoting architecture by customizing features of the context architecture and message sinks. A deeper understanding of contexts and message sinks can help you design more efficient and powerful .NET Remoting applications. We’ll use message sinks and contexts to prevent a client of a remote object from making a remote method call if the client passes invalid parameters to a method, thus saving a round-trip to the remote object. We’ll also look at how you can trace messages and log exceptions thrown across context boundaries.

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.

Table 6-1. Members of System.Runtime.Remoting.Messaging.IMessageSink

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.