32
.NET Remoting Customization Made Easy: Custom Sinks Introduction Isn’t .NET development easy? I, for one, believe that our life as developers is simpler with the .NET framework comparing to prior technologies such as COM / DCOM under C++. .NET Remoting is, without a doubt, an excellent example for this simplicity. After reading a comprehensive article or two chapters of your favorite remoting book, you can immediately begin creating powerful distributed applications. .NET Remoting also offers outstanding flexibility and various customization options. However, this is where simplicity ends. In my opinion, .NET Remoting is very easy to use but not all that easy to customize. To do so, you must be familiar with the inner plumbing of the .NET Remoting infrastructure and are required to write numerous lines of code you couldn’t care less about. Don’t get me wrong. I do believe that a more aware developer, one that understands the underlying development infrastructure, is essentially a better developer. I just don’t think that it means understanding and implementing every little detail as .NET Remoting customizations sometimes require. In this article I would like to present a small library, which simplifies one the most important aspects of the .NET Remoting customization: Custom Sinks. What are Sinks? When you work with a remote object, you do not hold a reference to that object, but a reference to a proxy. The proxy is an object that looks and feels exactly like the remote object and can convert your stack based method calls into messages, and send them to the remote object. In order to send a message to the remote object, the proxy uses a chain of sinks. It calls the first sink in the chain and provides it with the message. The sink optionally modifies the message, and passes it to the next sink, and so on. One of the sinks along the way is a formatter sink. The task of this special sink is to serialize the message into a stream. Sinks after the formatter sink operate on the stream, because at this point the message is no longer relevant (and is provided to the sinks as information only). The last sink in the stream is the transport sink, which is in charge of sending the data to the server and waiting for a response. When response arrives, the transport sink returns it to the previous sink and the response starts finding its

Remoting Custom Sinks

Embed Size (px)

DESCRIPTION

Working with Custom sinks in Remoting

Citation preview

Page 1: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

Introduction

Isn’t .NET development easy? I, for one, believe that our life as developers is simpler with the .NET

framework comparing to prior technologies such as COM / DCOM under C++. .NET Remoting is,

without a doubt, an excellent example for this simplicity. After reading a comprehensive article or

two chapters of your favorite remoting book, you can immediately begin creating powerful

distributed applications. .NET Remoting also offers outstanding flexibility and various

customization options. However, this is where simplicity ends. In my opinion, .NET Remoting is

very easy to use but not all that easy to customize. To do so, you must be familiar with the inner

plumbing of the .NET Remoting infrastructure and are required to write numerous lines of code

you couldn’t care less about. Don’t get me wrong. I do believe that a more aware developer, one

that understands the underlying development infrastructure, is essentially a better developer. I

just don’t think that it means understanding and implementing every little detail as .NET Remoting

customizations sometimes require. In this article I would like to present a small library, which

simplifies one the most important aspects of the .NET Remoting customization: Custom Sinks.

What are Sinks?

When you work with a remote object, you do not hold a reference to that object, but a reference

to a proxy. The proxy is an object that looks and feels exactly like the remote object and can

convert your stack based method calls into messages, and send them to the remote object. In

order to send a message to the remote object, the proxy uses a chain of sinks. It calls the first sink

in the chain and provides it with the message. The sink optionally modifies the message, and

passes it to the next sink, and so on. One of the sinks along the way is a formatter sink. The task

of this special sink is to serialize the message into a stream. Sinks after the formatter sink operate

on the stream, because at this point the message is no longer relevant (and is provided to the

sinks as information only). The last sink in the stream is the transport sink, which is in charge of

sending the data to the server and waiting for a response. When response arrives, the transport

sink returns it to the previous sink and the response starts finding its way back to the proxy. Along

the way, the response goes through the formatter sink, which deserializes the response back into

a response message.

What happens on the server side? You guessed right. The server also holds a chain of sinks. This

time the chain leads to the target object. The first sink is the transport sink. Along the way lies the

formatter sink and finally there is the stack builder which does exactly the opposite of the client-

side proxy. It converts the message into a stack based method call to the target object. When the

target object’s method returns, information (return value, ref parameters, etc) is packed into a

message, which is returned back through the same sink chain starting with the stack builder and

ending with the transport sink.

Page 2: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

In reality there are more sinks than the ones in the above figure, but I wanted to keep things

simple and chose to show only the ones relevant to the current discussion. Otherwise I would miss

the entire point of this article, wouldn’t I?

Custom Sinks

As shown in the above figure, it is possible to add custom sinks to the chain, both on the client

side and server side. So when do we decide to develop our own custom sink? We usually do it

when we want to inspect or modify the data sent from the proxy to the remote object and / or the

data returned from the remote object back to the proxy.

Let’s say that you want to encrypt the information sent over the wire between the client and the

server. To do so, you can create a client-side custom sink that encrypts outgoing request data and

decrypts incoming response data. You should also create a server-side custom sink that decrypts

request data arriving from the client and encrypts response data sent back to the client.

Custom sinks can be placed either before or after the formatter sink, depending on whether they

are designed to manipulate the message or the serialized stream. The encryption custom sink

would want to work on the stream (it doesn’t care about the logical meaning of the message; it

just needs to scramble it). Therefore the client-side custom sink should be situated after the

Page 3: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

formatter sink (after the message is serialized to a stream) whilst the server-side custom sink

should be situated before the formatter sink (before the stream is desterilized back to a message).

As another example, let’s say you want the client to send username and password information

and the server, which should examine them before allowing access to the target object. You could

add the username and password as parameters to every method of the target object. This would

effectively add the required information to the message, but would be rather cumbersome. As an

alternative, you can create a client-side custom sink that adds the username and password to

every outgoing message and a server-side custom sink that retrieves this information and throws

an exception (which will be propagated back to the client) if the username and / or password are

invalid. Since these custom sinks work on the message, rather than on the serialized stream, the

client-side custom sink should be placed before the formatter sink (before the message is

serialized to a stream) and the server-side custom sink should be placed after the formatter sink

(after the stream is desterilized back to a message).

Cool! How do I do it?

Here is the catch. In order to implement your own custom sink, you will work hard! You must

define a class that implements at least one of the IMessageSink, IClientChannelSink and

IServerChannelSink interfaces, depending on whether you define a client-side or server-side

custom sink and whether your sink will be situated before or after the formatter sink. You should

make sure to forward every call to the next sink. You must implement different logic for

synchronous and asynchronous calls. Furthermore, the way asynchronous calls are handled is a

completely different story for each of the above three interfaces. Wait! There is more. As a dessert

you should also define a Sink Provider class that implements another interface

(IClientChannelSinkProvider or IServerChannelSinkProvider) and is able to create instances

of your custom sink upon request from the .NET Remoting infrastructure.

These tasks are surely doable, but are quite tedious, time-consuming and error-prone. When I first

realized all I had to do here, I asked myself – couldn’t they provide a base class that takes care of

all of these details? Can’t I handle only my own business logic by implementing only the relevant

parts of the custom sink? Well, I didn’t find such a class in the class library, so I decided to write

one on my own. Admittedly, this class does not cover every custom sink scenario. By simplifying

things, you sometimes loose some flexibility. However, I believe that the class is valid for most

real-world custom sink scenarios. The class BaseCustomSink and friends are contained in the

CustomSink class library. You can download the library as well as sample derived custom sinks

and sample client and server with full source code.

Features

Page 4: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

The BaseCustomSink class, as its name implies, is a base class for custom sinks.

Main features:

Supports client-side and server-side sinks.

Supports synchronous and asynchronous calls.

Can be safely used in multithreaded applications.

Automatic reading of data from configuration file.

Allows derived classes to decide on runtime whether or not they want to be added to the

sink chain.

Calls a static initialization method of the derived class, if present.

These features will be further described and demonstrated in the following sections.

Basic Custom Sinks

In order to demonstrate the use of the CustomSink library, let’s implement encryption client /

server sinks. The source code for these sinks can be found in the accompanying SampleSinks

class library. Since I would like to concentrate in implementing the sinks, rather than delve into

cryptography, I will show the implementation of LameEncpriptionClientSink and

LameEncryptionServerSink, which simply add / subtract a delta from every byte in the stream,

as a simulation of encryption. In the future I may publish real-world encryption sinks, based on the

library presented here.

Before implementing the sinks, let’s create a helper class LameEncryptionHelper that will handle

the actual encryption. It will have a constructor that accepts the delta value, and two methods:

Encrypt and Decrypt. The Encrypt method looks as follows:

public Stream Encrypt(Stream source)

{

byte tempByteData;

int tempIntData;

MemoryStream encrypted = new MemoryStream();

while ((tempIntData = source.ReadByte()) != -1)

{

tempByteData = (byte)tempIntData;

tempByteData += this.delta;

encrypted.WriteByte(tempByteData);

}

encrypted.Position = 0;

return encrypted;

}

Page 5: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

The Decrypt method is almost identical, with one difference – it simply subtracts the delta

instead of adding it.

The BaseCustomSink class contains two virtual methods, ProcessRequest and

ProcessResponse. The ProcessRequest method is called when request data is on its way to the

target object and the ProcessResponse method is called when the response data is on its way

back to the proxy. You may override these methods in order to add your own processing.

protected virtual void ProcessRequest(

IMessage message,

ITransportHeaders headers,

ref Stream stream,

ref object state)

protected virtual void ProcessResponse(

IMessage message,

ITransportHeaders headers,

ref Stream stream,

object state)

As you can see, the parameter list of both methods is almost identical.

message – this is the message being transferred.

headers – created by the formatter sink and allows adding logical information after the

message has been serialized.

stream – the stream that contains the serialized message. We can modify the stream or

assign a new stream.

state – in the ProcessRequest we may assign the state to any object that we later need in

the ProcessResponse.

Examine the implementation of these methods for the LameEncryptionClientSink:

protected override void ProcessRequest(

IMessage message,

ITransportHeaders headers,

ref Stream stream,

ref object state)

{

stream = this.encryptionHelper.Encrypt(stream);

headers["LamelyEncrypted"] = "Yes";

}

Page 6: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

protected override void ProcessResponse(

IMessage message,

ITransportHeaders headers,

ref Stream stream,

object state)

{

if (headers["LamelyEncrypted"] != null)

{

stream = this.encryptionHelper.Decrypt(stream);

}

}

Both methods use a member field of the type LameEncryptionHelper in order to perform the

actual encryption. The ProcessRequest also adds information to the headers, specifying that the

stream was lamely encrypted. Here is the implementation for the LameEncryptionServerSink:

protected override void ProcessRequest(

IMessage message,

ITransportHeaders headers,

ref Stream stream,

ref object state)

{

if (headers["LamelyEncrypted"] != null)

{

stream = this.encryptionHelper.Decrypt(stream);

state = true;

}

}

protected override void ProcessResponse(

IMessage message,

ITransportHeaders headers,

ref Stream stream,

object state)

{

if (state != null)

{

stream = this.encryptionHelper.Encrypt(stream);

headers["LamelyEncrypted"] = "Yes";

}

}

Page 7: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

The code for the server sink is designed to be capable of working with any client, regardless of

whether they use the LameEncryptionClientSink. The ProcessRequest method therefore

examines the headers to determine whether the stream was lamely encrypted. In such case the

method performs two things: it decrypts the method and assigns true to the state, in order to

signal the ProcessResponse to encrypt the response. This way, only clients that sent encrypted

request streams will receive encrypted response streams. The ProcessResponse method

examines the state object to see whether it was assigned (the actual value doesn’t matter since it

can only be true or null), and if it was, encrypts the response stream.

That’s it! Our custom sinks are now ready to be used. I will demonstrate how the client and server

utilize the sinks using configuration files. Before I do that, I must say a word about providers.

The .NET Remoting infrastructure does not directly create custom sinks. Instead it creates a sink

provider class, which is able to create the custom sinks upon demand. The CustomSinks class

library contains two providers: CustomClientSinkProvider and CustomServerSinkProvider,

which are able to provide BaseCustomSink derived classes. You don’t need to worry about these

classes. Simply specify the appropriate one (client or server) in the configuration file.

filename: Basic_SampleClient.exe.config

<configuration>

<system.runtime.remoting>

<application>

<channels>

<channel ref="http">

<clientProviders>

<formatter ref="soap" />

<provider

type="CustomSinks.CustomClientSinkProvider, CustomSinks"

customSinkType="LameEncryption.LameEncryptionClientSink, SampleSinks" />

</clientProviders>

</channel>

</channels>

</application>

</system.runtime.remoting>

</configuration>

filename: Basic_SampleServer.exe.config

<configuration>

<system.runtime.remoting>

<application>

<channels>

<channel ref="http" port="7878">

Page 8: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

<serverProviders>

<provider

type="CustomSinks.CustomServerSinkProvider, CustomSinks"

customSinkType="LameEncryption.LameEncryptionServerSink, SampleSinks" />

<formatter ref="soap" />

</serverProviders>

</channel>

</channels>

</application>

</system.runtime.remoting>

</configuration>

As shown above, the provider is given the custom sink type using the customSinkType attribute.

The type is specified in the format “namespace.class, assembly”. In my sample, the custom sink

classes reside in a namespace named LameEncryption in an assembly named SampleSinks. The

client and server applications will invoke RemotingConfiguration.Configure and pass the

configuration file name as a parameter. Note that the sinks appear after the formatter in client

configuration file, and before the formatter in the server configuration file. This is crucial for the

sinks to function properly, since they must operate on the stream.

NOTE: I used an HTTP channel in my example, but you can easily switch to TCP.

You may now run the provided sample client and server application to see the custom sinks in

action. Actually you won’t see much, as the encryption will be done unnoticeably (after all, that’s

the whole point). However, I added some console outputs, which will prove that the sinks actually

work...

In this section, I demonstrated the relative ease of creating simple custom sinks using the

CustomSinks library. After having simplified things, I feel like complicating them a bit... For many

custom sinks, the basic features described in this section will suffice. However, to avoid resorting

to ‘manual’ implementation of custom sinks when we need just a little bit more functionality, I

added some more advanced features into the CustomSink library. You may stop here if that’s all

you need and refer to this article in the future.

In the following sections I will further develop the Lame Encryption sinks, to demonstrate

additional features of the CustomSinks library. Since I want to leave the simplest sample intact,

any further developments will be incorporated into EnhancedLameEncryptionServerSink and

EnhancedLameEncryptionClientSink (as if the original names weren’t long enough...). If you

wish to test the enhanced sinks, make sure to open SampleClient.cs and SampleServer.cs and

uncomment the relevant lines.

Accessing Configuration Data

Page 9: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

To design more general custom sinks, we may sometimes need to access data from a

configuration file. For example our Lame Encryption sinks may want to read the delta value (the

value added / subtracted to / from every byte in the stream). This can be easily accomplished

when deriving a sink from BaseCustomSink. If the configuration file contains a customData

element under the provider element, the custom sink will be able to retrieve it through its

constructor. Let’s review the following excerpt of the modified client and server configuration files

(for the sake of brevity, I present here only the clientProviders and serverProviders

elements):

Enhanced_SampleClient.exe.config

<clientProviders>

<formatter ref="soap" />

<provider type="CustomSinks.CustomClientSinkProvider, CustomSinks"

customSinkType="LameEncryption.EnhancedLameEncryptionClientSink, SampleSinks">

<customData delta = "15" />

</provider>

</clientProviders>

Enhanced_SampleServer.exe.config

<serverProviders>

<provider type="CustomSinks.CustomServerSinkProvider, CustomSinks"

customSinkType="LameEncryption.EnhancedLameEncryptionServerSink, SampleSinks">

<customData delta = "15" />

</provider>

<formatter ref="soap" />

</serverProviders>

In order to retrieve this data, the sink should have a constructor that accepts one parameter of

the type SinkCreationData. In such case, this constructor will be called and be provided with the

customData element through this parameter. Note that given the presence of such constructor,

the parameterless constructor will never be called (even if there is no customData element for the

appropriate sink).

Let’s review the constructor of the modified custom client sink

(EnhancedLameClientEncryptionSink). The server sink’s constructor is identical.

public EnhancedLameEncryptionClientSink(SinkCreationData creationData)

{

byte delta = 1;

if (creationData.ConfigurationData.Properties["delta"] != null)

{

delta = byte.Parse(

Page 10: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

creationData.ConfigurationData.Properties["delta"].ToString());

}

this.encryptionHelper = new LameEncryptionHelper(delta);

}

Self Exclusion from Sink Chain

One of the main differences between client and server sinks is the timing of their creation. Server

sinks are created once, when the channel is configured. Client sinks are created every time a new

proxy is created (every proxy may have a different chain of sinks). Thus, client sinks may be

created multiple times.

Your custom sink (either client-side or server-side) can prevent its addition to the sink chain in

runtime. If for some reason (after inspecting the customData for an instance), your custom sink

decides that it shouldn’t be a part of the chain after all, it may throw an ExcludeMeException

from its constructor. The provider will catch this exception and act accordingly. Furthermore, if

your custom sink decides that it should never be created again, it can be more specific. Instead

of throwing the ExcludeMeException every time its constructor is called, it can provide true to

the constructor of ExcludeMeException to signal that it wants to be excluded permanently. After

that, the provider will not even attempt to create an instance of the provider.

Notes:

1. The excludeMePermanently (the parameter of the ExcludeMeException’s constructor) is

relevant only to client-side sinks. It is ignored when thrown by server-side sinks.

2. The excludeMePermanently works on a per provider basis. If the same client-side sink

appears multiple times in the configuration file, for example in both HTPP and TCP

channels, and the custom sink’s constructor for the HTTP channel throws and exception

specifying it should be excluded permanently, it will only be excluded for the HTTP

channel. The provider will still attempt to create instances of this custom sink for the TCP

channel.

Obtaining Additional Sink Creation Parameters

Your custom sinks may obtain additional information upon their creation. This is done by having a

constructor that accepts one parameter of type ClientSinkCreationData or

ServerSinkCreationData (depending on the type of your custom sink). Both derive from

SinkCreationData, which I presented in the previous section. This constructor has precedence

over any other constructor. If it exists, it will be the only one to get called.

Page 11: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

The ClientSinkCreationData class contains the following fields: ConfigurationData – the

previously discussed SinkProviderData object, channel – the channel for which the sink is

created, Url – the URL of the remote object, and RemoteChannelData – data about the channel at

the server side (when applicable). You may refer to the parameters of

IClientChannelSinkProvider.CreateSink in the .NET Framework SDK documentation for

further details.

The ClientSinkCreationData class contains the following fields: ConfigurationData – the

previously discussed SinkProviderData object, and channel – the channel for which the sink is

created. You may refer to the parameters of IServerChannelSinkProvider.CreateSink in

the .NET Framework SDK documentation for further details.

Back to our Lame Encryption example, let’s say that we don’t need encryption when the target

object resides on “localhost”. Review the following constructor (which supports previously

developed functionality as well). The new constructor for EnhancedLameClientEncryptionSink is

a follows:

public EnhancedLameEncryptionClientSink(ClientSinkCreationData creationData)

: this((SinkCreationData)creationData)

{

Uri uri = new Uri(creationData.Url);

if (uri.IsLoopback)

{

throw new ExcludeMeException();

}

}

Notes:

Since the EnhancedLameEncryptionServerSink (and the basic

LameEncryptionServerSink for that matter) was already designed to support both

encrypted and unencrypted communication, it does not require any modifications.

The first constructor, the one that takes SinkCreationData as a parameter, is now

redundant (not listed here, but still present in the code). However I left it because it is

presented in previous sections. Since I already kept it, I redirected to it instead of writing

again the code for retrieving the delta from configuration file. However, for efficiency

reasons, you should normally first decide whether to throw an ExcludeMeException and

only afterwards perform additional construction logic.

Static Initialization

Page 12: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

Since client-side custom sinks may be created numerous times, you may sometimes want your

class to initialize as much static information as possible. This way, you refrain from processing the

same information for every newly created instance. Normally, when you need static initialization,

you add a static constructor to your class. You may certainly adopt this approach for your custom

sink. However it has one major drawback. If your static constructor throws an exception, this

exception cannot normally be caught and handled.

Here is my solution. Define the following method in your custom client-side sink:

public static void Init(SinkProviderData data, ref object perProviderState)

{ }

This method is guaranteed to be called before any instance of your custom sink is created. You

can now place the call to RemotingConfiguration.Configure in try / catch block and catch any

exception this method might throw. Moreover, this method is provided with the data from the

customData element in the configuration file. NOTE: although this method is more relevant to

client-side sinks, it is valid in server-side sinks as well.

Important: There is a major distinction between this method and a static constructor. If your

custom sink appears more than once in the configuration file, the Init method will be called

multiple times, once per occurrence. In such scenarios, you should avoid assigning data to static

fields, because every call to Init will override the fields values assigned in previous calls to. To

overcome this problem, use the perProviderState.

The perProviderState allows you to save data on a per provider basis. If your sink appears

multiple times in the configuration file, the .NET Remoting infrastructure will create multiple

instances of CustomClientSinkProvider, one for each occurrence. You can take advantage of

this behavior by assigning one data object of your choice to the perProviderState parameter of

the Init method. This way, your object will be held within the provider object and multiple calls to

Init (which are done from different instances of the provider) will not override the previously set

data. Your custom sink will be able to access the data through the inherited

BaseCustomSink.PerProviderData property.

The Init method is not demonstrated in the Lame Encryption sample. However the SampleSinks

project contains another example, The Credentials Sinks, which uses this feature. The Credentials

Sinks are described below.

A Deeper Look at ProcessRequest and ProcessResponse

Page 13: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

The first three parameters of ProcessRequest and ProcessResponse behave differently

depending on whether your custom sink is a server-side or client-side sink and whether it is

situated before or after the formatter sink. Here is the complete analysis.

ProcessRequest

Client-side, before the formatter sink:

message – request message.

headers – null.

stream – null. Do not assign this parameter to another stream.

Client-side, after the formatter sink:

message – request stream message. Do not modify the message as it has already been

serialized.

headers – request transport headers.

stream – request stream.

Server-side, before the formatter sink:

message – null.

headers – request transport headers.

stream – request stream.

Server-side, after the formatter sink:

message – request message.

headers – request transport headers.

stream – null. Do not assign this parameter to another stream.

ProcessReponse

Client-side, before the formatter sink:

message – response message.

headers – null.

stream – null. Do not assign this parameter to another stream.

Client-side, after the formatter sink:

message – null.

headers – response transport headers.

Page 14: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

stream – response stream.

Server-side, before the formatter sink:

message – response message. Do not modify the message as it has already been

serialized.

headers – response transport headers.

stream – response stream.

Server-side, after the formatter sink:

message – response message.

headers – null.

stream – null. Do not assign this parameter to another stream.

The Credentials Sinks

The Lame Encryption sinks presented throughout this article manipulate the stream of the request

and the response. For completeness, I also included the Credential Sinks, which demonstrate

message-based processing.

The task of the client-side sink is to add the username and password to every outgoing

communication. The server-side sink verifies these credentials and throws an exception if they are

found to be invalid. The server-side retrieves the credentials from the configuration file. The

client-side sink also retrieves the credentials from the configuration file. However, different

credentials can be assigned to different servers and even ports.

You are invited to review the DemoCredentialServerSink and DemoCredentialClientSink in

the SampleSinks project.

How to Build Custom Sink Providers for .NET Remoting

The 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

Page 15: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

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.

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.

Page 16: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

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

Page 17: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

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.

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:

<configuration>

<system.runtime.remoting>

<application>

<channels>

<channel ref="http">

<clientProviders>

<formatter ref="soap" />

<provider type=

"SerializationSink.ClientSinkSerializerProvider,

SerializationSink" />

</clientProviders>

</channel>

</channels>

<client>

<wellknown type="RemotingServer.RSExample, RSExample"

url="http://localhost/RemotingExample/RSExample.soap" />

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.

Page 18: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

</client>

</application>

</system.runtime.remoting>

</configuration>

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:

<SOAP-ENV:Envelope

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:xsd="http://www.w3.org/2001/XMLSchema"

xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"

xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"

xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0"

SOAP-ENV:encodingStyle=

"http://schemas.xmlsoap.org/soap/encoding/">

<SOAP-ENV:Body>

<i2:GetHelloWorld id="ref-1"

xmlns:i2=

"http://schemas.microsoft.com/clr/nsassem/RemotingInterface.

IRemotingExample/RemotingInterface">

</i2:GetHelloWorld>

</SOAP-ENV:Body>

</SOAP-ENV:Envelope>

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

Page 19: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

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.

Securing Remoting Calls and Validating Client

.NET Classes used :

System.Runtime.Remoting

System.Runtime.Remoting.Messages

System.Runtime.Remoting.Contexts

System.Reflection

Page 20: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

Introduction

We are making a smart-client application, where the assemblies will be installed to a central, Remoting server. The client,

when it connects to the server, will download the assemblies that are not there already or those assemblies that have

been updated on the server. And we are making it like a framework and so authorized third-party vendors can also come

in to the picture. They can also make their own assemblies, which should be part of the server and then downloaded to

the client. Also, much application-specific information, including the SQL connection information, will be downloaded via

an XML “catalog”.

The Risk

And there comes a risk. The risk of “somebody else” (in other words, any “un-authorized application) trying to connect to

our Remoting server and downloads the information. Or it can be such that those “un-authorized” applications,

referencing our assemblies and then trying to download the information through the back-door entry. We needed a way to

prevent anything of this kind happening.

Thoughts on different security aspects

We needed a custom security mechanism to prevent this. Thinking about such a security mechanism and I already had to

take care of the following things in my mind.

I first thought of using the StrongNameIdentityPermission attribute, given by the .NET framework itself. But the

problem is that we are allowing third party vendors to develop around our framework and hence assemblies with different

strong-name public key need to call other assemblies. The StrongNameIdentityPermission attribute allows only strong

named assemblies with one public key to call the assembly in question.

There were already some Remoting objects created. Specifying a security mechanism should be made easily

applicable to those and of course to future objects.

There should be a centralized way to bring a security mechanism where the Remoting objects should not worry about

it, but a layer above it should. So that changes to those security features can be made easily (Remember that the security

feature is not complete at any point of time. It is of evolving in nature).

And this is how I decided to move on…

Now, it became clear to me that to secure the Remoting calls, we need to validate the client. There should be some

information to be sent from the client, on which the security mechanism has to validate it. And it should be foolproof too.

Because I am asking the client itself to tell the truth!! (And which era I am living in, meanwhile?). Also, not all methods of

all Remoting objects can worry about calling a method to validate the client (and my colleagues were not ready to change

all their code too, to be honest!!). After taking care (read “worrying”) of all these things, I arrived at this kind of a security

mechanism.

The Security Mechanism

The remoting client, for each call, will set the Stack Trace of the client into a Call Context Data. For more information of

what is Call Context Data and why it is used, please refer my earlier articles. The Stack Trace is the one, which the client

Page 21: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

cannot alter (but can reduce the number of stack frames, which is again a potential risk. We will discuss this later in the

article).

The call context data will get passed to the server for every remoting call. The server, on the other side, will take the stack

trace from the call context data. Then, the server will take each assembly of the stack trace, check its public key and

verify the same with the server side assembly’s public key. If they are same, the call is allowed (meaning only our

assembly has made the remoting call). If the client assembly is not having a public key or it is different from the server

side assembly, we assume that something is wrong and we throw a Security Exception to the client. The same is the

case if the Stack Trace is not sent at all by the client.

Well, the approach is OK. But who will take care of the validation done on the server side? Will every method of every

Remoting object do that? Or is there a way to commonly tackle this?

The answer is Yes. The solution is to “Intercept” the Remoting calls that come to the server, before the actual method is

called on the Remoting object. We will validate the Stack Trace, decide whether to allow the method to be called or not. If

we do not want to, we throw a Security Exception at this point itself. The implementation is done by using

DynamicProperty and DynamicSink.

Intercepting Remoting Calls using Dynamic Property and Sink

Now, so much explanation have been done in the first half of this article, let’s jump into some technical stuff in the

second. If you have decided to go for a sleep by now, please give me a last chance and let me pace up this article by

showing the implementation stuff!!

Dynamic Property

The System.Runtime.Remoting.Contexts namespace has an interface called IDynamicProperty and

IContributeDynamicSink interfaces. While the first one has only one read-only property called Name , the second has also

only one method GetDyanmicSink, which returns an instance of IDynamicMessageSink.  We will be creating a class that

implements both these interfaces. And this is the class that we are going to “Register” for interception. To sum up, our

class (called as DynamicProperty) will look like this:

Imports System.Runtime.Remoting.Contexts

Imports System.Runtime.Remoting.Messaging

Imports System.Reflection

Public Class DynamicProperty

   Implements IDynamicProperty, IContributeDynamicSink

   Private Const SINK_NAME As String = "MyDynamicSink"

   Public ReadOnly Property Name() As String Implements

System.Runtime.Remoting.Contexts.IDynamicProperty.Name

       Get

           Return SINK_NAME

Page 22: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

       End Get

   End Property

   Public Function GetDynamicSink() As

System.Runtime.Remoting.Contexts.IDynamicMessageSink Implements

System.Runtime.Remoting.Contexts.IContributeDynamicSink.GetDynamicSink

       Return New DynamicSink

   End Function

End Class

Have a look at the GetDynamicSinkmethod. It returns an instance of a class called DynamicSink. But where is it? We will

create one now.

Dynamic Sink

The DynamicSink class implements an interface called IDynamicMessageSink. This has two methods -

ProcessMessageStart and ProcessMessageFinish. If you have guessed correctly, the first method will fire whenever a

Remoting method is going to get called. The next method will fire whenever a Remoting method is called and the call is

about to get returned to the client.

Naturally, we need to intercept the call before it is being actually called by the Remoting object itself. So, we will place our

code in the ProcessMessageStart method.

The DynamicSink class will look like this:

Public Class DynamicSink

   Implements IDynamicMessageSink

   Public Sub ProcessMessageFinish(ByVal replyMsg As

System.Runtime.Remoting.Messaging.IMessage, ByVal bCliSide As Boolean, ByVal bAsync As

Boolean) Implements

System.Runtime.Remoting.Contexts.IDynamicMessageSink.ProcessMessageFinish

       Try

       Catch ex As Exception

           Throw ex

       End Try

   End Sub

   Public Sub ProcessMessageStart(ByVal reqMsg As

System.Runtime.Remoting.Messaging.IMessage, ByVal bCliSide As Boolean, ByVal bAsync As

Boolean) Implements

System.Runtime.Remoting.Contexts.IDynamicMessageSink.ProcessMessageStart

       Try

Page 23: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

‘----------------- STEP 1 -------------------------------------------

           Dim Props As ArrayList = CType(CType(CType(reqMsg.Properties,

System.Collections.IDictionary).Values, System.Collections.ICollection),

System.Collections.ArrayList)

‘----------------- STEP 2 -------------------------------------------

           Dim i As Int32

           For i = 0 To Props.Count - 1

               If Not Props(i) Is Nothing AndAlso Props(i).GetType Is

GetType(LogicalCallContext) Then

                   Dim callcontext As LogicalCallContext = CType(Props(i),

LogicalCallContext)

                   If callcontext Is Nothing Then

                       Throw New Security.SecurityException

                   Else

‘----------------- STEP 3 -------------------------------------------

                       Dim ContextData As ICallContextData2 =

CType(callcontext.GetData(“UserInfo”), ICallContextData2)

                       If ContextData Is Nothing Then

                           Throw New Security.SecurityException

                       Else

                       

‘----------------- STEP 4 -------------------------------------------

Dim secHlpr As New SecurityHelper

  If Not secHlpr.IsAssemblyCorrect(ContextData.StackTrace) Then

                               Throw New Security.SecurityException

                           Else

                               Exit Sub

                           End If

                       End If

                   End If

               End If

           Next

           Throw New Security.SecurityException

       Catch ex As Exception

           Throw ex

       End Try

   End Sub

End Class

I know I just can’t win your hearts if I don’t explain the things. Just have a look at the ProcessMessageStart method

carefully. For a better understanding, I have split up the logic into 4 steps.

Page 24: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

Step 1:

The ProcessMessageStart method contains an IMessage component. This contains details about the current method call.

The IMessage interface has a property called Properties (IMessage.Properties), which is a Dictionary object. This

Dictonary object’s “Values” property is an ArrayList of different information. One of them (and presumably last of them) is

the LogicalCallContext.

Step 2:

So, we are just looping through the properties until we find the LogicalCallContext in the ArrayList. In case we are unable

to find one, we just throw a Security Exception from here, which prevents the actual method being called on the Remoting

object.

Step 3:

Once we find the LogicalCallContext, we are getting the actual data by using the LogicalCallContext.GetData() method.

We are just type casting to an instance of ICallContextData2 interface (which is created by us and that contains a

property called StackTrace, which contains the client Stack Trace. You can find this file in the downloaded zip file)  In

case the GetData method returns Nothing, we are again throwing a Security Exception.

Step 4:

If we have found one, we are passing this stack trace to a method called IsAssemblyCorrect which loops through the

stack trace and check against the same assembly in the server side to see whether both have the same public keys are

not.  This method exists in a class called SecurityHelper, designed primarily to take care of these things. You can find the

class in the downloaded zip file.

Register the Remoting object for Interception

Now we have our Interception classes ready, we need to make a small change in our Remoting object’s constructor for

this to work. In the constructor of the Remoting object, call the RegisterDynamicProperty method of the Context object.

Assume that you have a Remoting object called MyRemotingObject, you would write the following code in the

constructor:

   Public Sub New()

Context.RegisterDynamicProperty(New DynamicProperty(),Me,Nothing)

   End Sub

And this is all that you need to do for intercepting all the Remoting calls for your Remoting object, MyRemotingObject.

The Client Side Implementation

The client side implementation is very simple. Before initiating a call to the MyRemotingObject class, you need to set the

Call Context Data. Setting the Call Context Data is very simple. Here is how you should do:

MyObj = CType(Activator.GetObject(GetType(MyRemotingObject), remotingUrl),

MyRemotingObject)

Page 25: Remoting Custom Sinks

.NET Remoting Customization Made Easy: Custom Sinks

Dim CallCtxt As ICallContextData2 = MyRemotingObject.CallContextData

‘This property will expose the ICallContextData2 interface from your ‘Remoting object.

CallCtxt.StackTrace = New StackTrace

‘Set the client’s Stack Trace to the property

CallContext.SetData(“UserInfo”,CallCtxt)

‘Name should be same as that of the server.

MyRemotingObject.HelloWorld()

‘Calling this method will pass on the stack trace to the server. You

‘need to have all the assemblies in the stack trace on the server side

‘also. This constraint itself prevents “un-authorized” or Non-Server

‘assemblies from calling your code!!

Stack Trace – A caution

If the client decides to shave off a part of the stack trace (by using the SkipFrames parameter of the StackTrace

constructor, this means that the stack trace may not have the “un-authorized” assembly’s information and therefore, the

server would validate fine. The Security Helper class will try to avoid checking for .NET related assemblies (like

System.Windows.Forms.dll etc). But what happens if the Stack Trace contains only .NET assemblies? Because the client

has “intelligently” taken some frames out, this is another potential risk. Therefore, the Security Helper class, which I have

given, will return true only if there is at least one assembly, which is other than the .NET, related assemblies.

Files For Download

This article accompanies some files put into a zip file. This zip file contains the following classes:

SecurityHelper – Which takes care of mapping client and server side assemblies and their public keys respectively.

CallContextData – Which Implements ICallContextData2 interface

ICallContextData2 – Interface, which has a property for getting the Stack Trace.

DynamicProperty – The one responsible for registering ContextBoundObjects.

DynamicSink – The one where we actually validate the stack Trace.