Back to .NET remoting: TypeFilterLevel
A couple of days ago I got mail from a reader of an old .NET remoting article of mine, where I built a sample chat application using the precursor technology of WCF. The article was pretty old, and the sample code written in .NET 1.0 and Visual Studio .NET 2002. The code would not work under .NET Framework 1.1, as the reader Henk Middeldorp found out. The exception message is "Because of security restrictions, the type System.Runtime.Remoting.ObjRef cannot be accessed.".
The chat server would not work any more because of a security change in .NET FX 1.1. To be more precise, custom types that are transferred across a remoting channel are not allowed (by default) to be deserialized automatically. You can read more about automatic deserialization on MSDN here. The basic idea is that there are two levels of deserialization filters: Low (the default) and Full. When you have types other than primitives or remoting infrastructure types you will need to switch to the Full deserialization level.
The chat application consists of a client Windows Forms application and a console application server. Upon connection of a client to the server, it serializes a ObjRef to the main form to the server. The server will hold the reference and use it to communicate back to the client if necessary. The client application will crash when the server will not allow the deserialization of the ObjRef to the ChatForm object.
There are two ways to do set the deserialization level: through configuration or programmatically. There is some guidance at MSDN, but I hope to clarify some points.
Setting automatic deserialization filter level by configuration
Depending on the serialization format you use need to set either or both formatter server providers to typeFilterLevel="Full". The most common scenario is SOAP for HTTP channels and binary for TCP channels. I've included both for the server. For two-way communication chances are that the server and client also have their respective roles reversed, i.e. the server becomes client and the client sometimes is server to incoming calls of the server. In the fragment below I only set the client provider binary formatter to Full.
<?xml version="1.0" encoding="utf-8" ?>
<channel ref="tcp" port="2000">
<formatter ref="soap" typeFilterLevel="Full" />
<formatter ref="binary" typeFilterLevel="Full" />
<formatter ref="binary" />
<wellknown mode="Singleton" type="Chat.RemoteChat, ChatLibrary" objectUri="ChatServer" />
Since it was a long time ago that I did .NET remoting I had a false picture of configuration. I have gotten used to the WCF configuration, where it is possible to mix and match code and declarative configuration. In other words: WCF allows you to put smaller or larger parts of your setup in configuration and complement it with code. You can go all code or almost all configuration and just about anything in between. For .NET remoting you can only go for code only and config only.
This means that you have to define the server in configuration as well. Notice the well-known service of the chat server type that will be hosted at a TCP endpoint "ChatServer" on port 2000, giving a full endpoint address of tcp://localhost:2000/ChatServer (where localhost can be the IP address of any network interface card). All that remains to finish the setup is a call to load the configuration at startup of the server:
Dim filename As String = "ChatServer.exe.config"
All will be fine. The client configuration is pretty much the same, except for the <client> element instead of <service>.
<wellknown type="Chat.RemoteChat, ChatLibrary" url="tcp://localhost:2000/ChatServer" />
This is remarkable when you think of it. Why would the client need to set the typeFilterLevel to Full as well?. It is not strange when you think of the two-way communication and the symmetric exchange of data. Remarkably enough you can successfully communicate with the service using a single client even when you do not set the <serverProviders> section for the client. However, connecting a second client and
Programmatic setting of deserialization level
Things don't change that much when you programmatically set the filter level. The piece of code that is needed for the server is listed below.
Dim serverProvider As New BinaryServerFormatterSinkProvider
serverProvider.TypeFilterLevel = TypeFilterLevel.Full
Dim clientProvider As New BinaryClientFormatterSinkProvider()
Dim properties As IDictionary = New Hashtable()
properties("port") = portNumber
Dim channel As New TcpChannel(properties, clientProvider, serverProvider)
Again, the client-side configuration is the same, except for the port and the instantiation of the remote server activated object (SAO). The port is zero to allow a free port to be picked automatically. This was also the case for the configuration scenario.
Dim properties As IDictionary = New Hashtable()
properties("port") = 0
channel = New TcpChannel(properties, clientProvider, serverProvider)
chatServer = DirectCast(Activator.GetObject(GetType(RemoteChat), serverUri), RemoteChat)
Be careful when you set the typeFilterLevel to Full. Remember that the Low level is there for a reason. Check the list in the MSDN article to see if you really need to. In the case of MarshalByRefObject instances that are passed as ObjRef there is only one situation where you can stick with Low: if a client-activated object is requested by the client and the server passes the created object by means of an ObjRef. Only in this case can the client deserialize the ObjRef on the Low level.
One of the other changes to .NET remoting since .NET FX 1.1 is that the ChannelServices.RegisterChannel method has an extra overload. The new overload excepts an additional boolean parameter ensureSecurity, that will makes sure that security is enabled. This means that when True is passed the channel must implement ISecurableChannel and encryption and digital signatures will be enabled. All variations of TcpChannel, HttpChannel and IpcChannel types implement ISecurable, except for HttpServerChannel.
Check the improved Chat server sample in the attachments. To play with the various options, change the defined constant from CONFIGURATION to PROGRAMMATIC or remove it to reproduce the exception. It is fun to examine the example and see where we have gone with WCF since .NET remoting. Have fun.