Remoting Custom Sinks
Remoting Custom Sinks
Remoting 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.
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).
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:
These features will be further described and demonstrated in the following sections.
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:
{
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.
As you can see, the parameter list of both methods is almost identical.
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:
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.
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).
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:
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.
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.
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:
Notes:
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:
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.
ProcessRequest
• message – request stream message. Do not modify the message as it has already been
serialized.
• headers – request transport headers.
• stream – request stream.
• message – null.
• headers – request transport headers.
• stream – request stream.
ProcessReponse
• message – null.
• headers – response transport headers.
• stream – response stream.
• message – response message. Do not modify the message as it has already been
serialized.
• headers – response transport headers.
• stream – response stream.
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.
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.
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.
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.
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:
<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.
• 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.
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).