Remoting and common classes

I’ve been playing a bit lately with .NET Remoting and I must say that so far I’m deeply un-impressed. The documentation seems to be sparse at best and downright misleading at worst. So I thought I’d put a few of my discoveries here so that people can learn from my mistakes.

.NET Remoting is a technology that allows the .NET runtime to code in another AppDomain or even another remote machine without having to worry about the details. At least that’s the theory. The problem is when you want to make calls that rely on custom classes or have a shared base class but different concrete implementations of that class on client and server side.

Normally I’d just use an interface and be done with it. That sounds easy right? We’ll have a nice interface in our common area and then client and server can implement the interface and it’ll all be good. To a certain value of work this will work although it’s not as neat as it could be and passing classes that can be subclassed around via the interface can get painful as you’ll see later.

Client:

using System;
using System.Runtime.Remoting;
using RedGate.Common;

namespace RedGate.Client
{
    public class Client
    {
        public void Main()
        {
            RemotingConfiguration.Configure("RedGate.Client.exe.config", false);
            // This sadly won't work with an interface...
            // IFruit fruit = new IFruit();
            IFruit fruit = (IFruit)Activator.GetObject(typeof(IFruit),
                        "tcp://richard:15200/Apple.rem");
            Console.WriteLine(fruit.GetName());
        }
    }
}

There are a few key things to note here. If we use an interface we can’t simply create the interface like we would any other object so the client config file becomes almost useless as we have to use the Activator.GetObject() call instead. Also I’ve set the typeFilterLevel property to Full or we wouldn’t be able to pass our nice simple Tree class around instead we’d get a very scary looking SecurityException. Also notice that I have to set the Serializable attribute otherwise the .NET runtime wouldn’t know how to throw my Tree class over the wire.

The config file for the client however isn’t completely useless even though there’s no way for me to use the <wellknown/> element. Bizarrely I’ve set a <serverProvider/> for the client. This will allow things like a Stream object to be passed over the wire and allow the call backs necessary to make it work although I don’t make use of it in the example.

The config file in server is loaded from the directory of the location of the assembly. This is because in my implementation my server is a windows service. However the working directory of a windows service when run is c:windowssystem32 which means unless I change the working directory I can’t load the config file. Another little gotcha that had me confused for a while. 

All is good in remoting land until the client decides that Tree simply isn’t enough and needs to subclass it to a Shrub (this little bit of information took me 4 hours to work out yesterday so listen closley). Well the interface still takes a Tree object so all should be well, however it won’t be, because the marshaller sends over the details of the runtime type rather than the interface type so the server will attempt to deserialize a Shrub class which it doesn’t know anything about. The work-around that seems to work at the moment is to implement ISerializable for the Tree class as follows…

Shared:

using System.Runtime.Serialization;

namespace RedGate.Common
{
    public interface IFruit
    {
        string GetName();
        string GetFruitForTree(Tree tree);
    }

    [Serializable]
    public class Tree : ISerializable
    {
        string LeafColor;
        string Height;
        string LatinName;

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            // Setting this type forces the de-serialise to happen on the common type
            info.SetType(typeof(Tree));

            info.AddValue("LeafColor", LeafColor);
            info.AddValue("Height", Height);
            info.AddValue("LatinName", LatinName);
        }

        protected Tree(SerializationInfo info, StreamingContext context)
        {
            LeafColor = info.GetString("LeafColor");
            Height = info.GetString("Height");
            LatinName = info.GetString("LatinName");
        }
    }
}

Client:

using System;
using System.Runtime.Remoting;
using RedGate.Common;

namespace RedGate.Client
{
    public class Client
    {
        public void Main()
        {
            RemotingConfiguration.Configure("RedGate.Client.exe.config", false);
            // This sadly won't work with an interface...
            // IFruit fruit = new IFruit();
            IFruit fruit = (IFruit)Activator.GetObject(typeof(IFruit),
                        "tcp://richard:15200/Apple.rem");
            Console.WriteLine(fruit.GetName());
            Shrub shrub = new Shrub();
            shrub.Shape = "Round";
            shrub.LeafColor = "Green";
            shrub.Height = "4 feet";
            shrub.LatinName = "Ribes oxyancanthoides";
            Console.WriteLine(fruit.GetTree(shrub));
        }
    }

    public class Shrub : Tree
    {
        string Shape;
    }
}

 The important part here is of course is the call to SetType in the GetObjectData for the shared implementation of the Tree object. This ensures that the marshaller will try to instantiate an object of type Tree and not Shrub.

Hopefully this article has given you some ideas about .NET Remoting so that at least you won’t make the same mistakes or get stuck at the same points that I have in the last few days.

Not sure if any of this code works as I’ve created the rather dubious example from hacking around code from the project that I’m working on. Still it’ll give you the basic gist of how to go about these things.