Sie sind auf Seite 1von 21

.

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
.NET Remoting Customization Made Easy: Custom Sinks

packed into a message, which is returned back through the same sink chain starting with the
stack builder and ending with the transport sink.

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.
.NET Remoting Customization Made Easy: Custom Sinks

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
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
.NET Remoting Customization Made Easy: Custom Sinks

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

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)
.NET Remoting Customization Made Easy: Custom Sinks

{
tempByteData = (byte)tempIntData;
tempByteData += this.delta;
encrypted.WriteByte(tempByteData);
}
encrypted.Position = 0;
return encrypted;
}

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.
.NET Remoting Customization Made Easy: Custom Sinks

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";
}

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,
.NET Remoting Customization Made Easy: Custom Sinks

ITransportHeaders headers,
ref Stream stream,
object state)
{
if (state != null)
{
stream = this.encryptionHelper.Encrypt(stream);
headers["LamelyEncrypted"] = "Yes";
}
}

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>
.NET Remoting Customization Made Easy: Custom Sinks

</channels>
</application>
</system.runtime.remoting>
</configuration>

filename: Basic_SampleServer.exe.config

<configuration>
<system.runtime.remoting>
<application>
<channels>
<channel ref="http" port="7878">
<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
.NET Remoting Customization Made Easy: Custom Sinks

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

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>
.NET Remoting Customization Made Easy: Custom Sinks

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(
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.
.NET Remoting Customization Made Easy: Custom 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.

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();
}
}
.NET Remoting Customization Made Easy: Custom Sinks

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

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
.NET Remoting Customization Made Easy: Custom Sinks

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

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:


.NET Remoting Customization Made Easy: Custom Sinks

• 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.
• 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
.NET Remoting Customization Made Easy: Custom Sinks

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
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
.NET Remoting Customization Made Easy: Custom Sinks

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:
.NET Remoting Customization Made Easy: Custom Sinks

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
You can apply
the next sink in the sink chain as a parameter to the constructor. That enables
serialization sink
the ClientSinkSerializer class to perform its processing and then pass the
techniques similar to
message data onto the client's transport sink.
those shown in this
To make use of the client sink provider, you must configure the remoting client article to enable
application using a configuration file entry so the application can message compression,
programmatically register the message sink provider. The application uses the message level
channel definition parameters defined in the client application's configuration file encryption, message
to create the sink on the client as soon as it registers the channel. Here's a logging, and other
sample configuration file excerpt: useful functions.
.NET Remoting Customization Made Easy: Custom Sinks

<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" />
</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"
.NET Remoting Customization Made Easy: Custom Sinks

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
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
.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 cannot
alter (but can reduce the number of stack frames, which is again a potential risk. We will discuss this later in the article).
.NET Remoting Customization Made Easy: Custom Sinks

Das könnte Ihnen auch gefallen