devxlogo

How to Build Custom Sink Providers for .NET Remoting

How to Build Custom Sink Providers for .NET Remoting

he remoting framework in .NET contains a feature set designed to support intra-process communications and application integration functions; however, for many applications the default remoting behaviors may not deliver all the features that you need. For example, what if you want to implement an encryption scheme that secures the messaging layer for your remoting application? To do so, you can make both the client and the server application aware of the encryption and decryption requirements for the messages, but that will require some redundant code for both client and server that you can’t easily leverage across applications. In contrast, using the extensibility features built into the .NET remoting framework lets you customize the remoting processes to suit your application’s needs. This article shows you how to build a custom message serialization sink for a sample remoting application that captures SOAP messages and serializes data to the remoting client’s file system.

Understanding .NET Remoting Infrastructure
To understand how to extend.NET remoting using custom sinks, you must first understand a little about how the remoting infrastructure works. The basic building blocks of the remoting architecture include:

  • Proxy objects used to forward calls to remote objects
  • Message objects used to carry the necessary data to invoke a remote method
  • Message sink objects used to process messages for remote method calls
  • Formatter objects used to serialize the message formats for transfer
  • Transport channel objects used to transfer the serialized messages from one process to another.
Using the extensibility features built into the .NET remoting framework lets you customize the remoting processes to suit your application’s needs.

For the remoting extension example in this article, pay close attention to the message sink objects and the formatters and transport channels, because they are key features in enabling the remoting infrastructure to support method invocations. Each call to a remote object reference generates a message that serves as a container for data exchanges. Message objects are dictionary objects encapsulated by the IMessage interface. Each message involved in a remoting call passes through a chain of message sinks. Message sinks are objects that cooperate to receive notifications for messages and process those messages. There are sinks on both the client and server sides of a remoting call. Each message sink passes the message to the next sink in the chain both before and after the message is transported.

Included in these message sinks are formatter sinks which serve to encode the data contained by the message into a format suitable for transmission over the wire. By default, .NET remoting supports both a SOAP formatter sink and a binary formatter sink, but you can extend the remoting infrastructure to support custom formatters.

Constructing a Remoting Application
You can implement three different interfaces for message sinks: IMessageSink, IClientChannelSink, and IServerChannelSink. The difference between these interfaces is that the IMessageSink interface can access and change the original dictionary object regardless of the serialization format, whereas the other two interfaces have access to the serialized message represented as stream objects only. Another difference is that the IClientChannelSink makes a distinction between synchronous and asynchronous calls during the request processing phase; but IServerChannelSink does not make that distinction. The sample project demonstrates how to implement a data serialization sink derived from IClientChannelSink.

To implement the ClientSinkSerializer, first create a simple remoting server with a single method exposed. Here’s an example.

   public interface IRemotingExample   { string GetHelloWorld(); }      public class RSExample : MarshalByRefObject, IRemotingExample   {      public string GetHelloWorld()      {         return "Hello World";      }   }

The RSExample class has a single GetHelloWorld method, which returns the string “Hello World”. The sample code defines the IRemotingExample interface in a separate assembly so it can be shared by the client and the server. I’ll use IIS to host the remoting server application. To configure the application, create a virtual directory with a bin subdirectory, and copy the assemblies containing the remoting server class and the interface to that bin directory. Then create a Web.config file, as shown in Listing 1, and place it in the virtual directory. The configuration file lets you specify the type, assembly name, and URI for the remoting server.

Next you’ll construct a client application to consume the remoting server. The sample uses a Windows forms application with one form containing a Button and a Label control. Add the event-handler code shown in Listing 2 to handle the button’s Click event. Finally, add the following line of code to the form’s constructor:

   RemotingConfiguration.Configure(filepath);

Calling the Configure method and passing a string parameter specifying the location of the client application’s configuration file lets you specify important details for the remoting infrastructure, such as the URI for the remoting endpoint, the channel type (HTTP or TCP) to use for communications, and the formatter type to use for message formatting. After constructing the client application, you should be able to run it and invoke the remoting server’s GetHelloWorld method.

You now have a working remoting application, and you’re ready to add in the custom sink and sink provider to save the incoming message contents using the remoting framework’s extensibility.

Creating the Custom Sink Provider
To start, you’ll need to build a class that can save message contents to a file using the serialized stream format accessible through the implementation of the IClientChannelSink interface. The reason for using the IClientChannelSink interface is that it provides the required functions and properties for client channel sinks, and by implementing this interface you can customize your own sinks. This interface defines a ProcessMessage method that you can use to extend the message sink and serialize the message contents to the file system (see the ClientSinkSerializer class in Listing 3). To implement this custom sink, you need to create a sink provider class that implements IClientSinkProvider. In the CreateSink member function of IClientSinkProvider, use the following lines of code:

   public IClientChannelSink CreateSink      (IChannelSender channel, string url, object remoteChannelData)   {      IClientChannelSink next = _nextProvider.CreateSink         (channel, url, remoteChannelData);         return new ClientSinkSerializer(next);   }

The preceding code returns a new ClientSinkSerializer class instance, passing the next sink in the sink chain as a parameter to the constructor. That enables the ClientSinkSerializer class to perform its processing and then pass the message data onto the client’s transport sink.

You can apply serialization sink techniques similar to those shown in this article to enable message compression, message level encryption, message logging, and other useful functions.

To make use of the client sink provider, you must configure the remoting client application using a configuration file entry so the application can programmatically register the message sink provider. The application uses the channel definition parameters defined in the client application’s configuration file to create the sink on the client as soon as it registers the channel. Here’s a sample configuration file excerpt:

                                                                                                                             

The element contains the defined channel properties, including the definition of the element and its nested element, which has attributes for the message sink provider types and assembly names. In addition, the element defines the formatter type and channel type for communications, which are SOAP and Http respectively in this example. When the client application is configured to use the sink provider, the message contents will be serialized to a file prior to the invocation of the remoting server method. The data captured from the responseStream when calling the GetHelloWorld method is the following:

                  

The message is in a SOAP format?as expected based on the configuration of the client and the remoting server. The body of the SOAP message specifies the remoting server method being invoked, GetHelloWorld.

The extensibility of the .NET remoting framework, including the use of custom sink providers for client applications consuming remoting services, lets you leverage the base plumbing of the remoting infrastructure to build custom applications, tweaking behavior as needed. You can use similar remoting extension techniques to develop custom formatters and add customization to remoting transports, meaning you can take advantage of different serialization formats and transport mechanisms for your remoting applications. You can apply serialization sink techniques similar to those shown in this article to enable message compression, message level encryption, message logging, and other useful functions. In addition, using the configuration support provided for remoting applications lets you take advantage of the extensibility features and the “plug-in” architecture long after developing and deploying the core application components.

   
devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist