S h o r t S t o r i e s

// Tales from software development

The 15 minute guide to remoting events

leave a comment »

This started off as The 5 minute guide to remoting events but there was more to cover than I had anticipated.

The project I’m currently working on is a Windows Service that performs some file processing every five minutes or so. Although it logs activity to a text file the customer wanted a more interactive status indicator. Obviously, a Windows Service should not display a UI so that meant a separate app that communicated with the service, most likely using .NET Remoting. I know that WCF is the way to go but I’d had enough of a struggle just to get the customer to agree to installing the .NET 2.0 runtime on their PCs so it’d have to traditional .NET Remoting.

I settled on a tray icon application and reworked some .NET Remoting code I had but hit a few problems in trying to implement client side event handlers for events raised on the server.

This stuff ought to be easier than it is although, in truth, it’s not that hard, just a bit obscure. The trouble is, Microsoft doesn’t provide much guidance or any specific examples of how to do it. I quickly skimmed through the books that usually not too far from my desk – Troelsen’s “Pro C# and the .NET Platform” (Third edition for .NET 2.0) and Richter’s “CLR via C#” – but didn’t find anything particularly helpful.

I trawled the internet for a while but got more confused than when I started because the examples that are out there are overly complex and often written by people who seem to have only a rudimentary grasp of .NET Remoting.

So, what needs to be done to handle remoting events ?

Event wrapper class

The first problem is that when your remoted class raises an event the delegate that’s called is referencing a method on the client side but the remoted class is (obviously) executing on the server. This results in an attempt to marshall your code (the instance of the class that contains the event handler) from the client to the server. First of all, that’s very unlikely to ever work, and, secondly, this isn’t the behaviour we want. The solution is to have a third class that is known to both the client and server and can be marshalled from the client to the server. This class is effectively a wrapper for the event. Your client creates a local instance of this class (i.e. not a remoted class created using the Activator.GetObject() method).

The wrapper class should derive from MarshalByRefObject and needs to implement the same event as the remoted class and a handler for the remoted class’s event. It then relays the remoted class’s event.

The first version of your code probably looked similar to this:

// Call local method to create class using Activator.GetObject()
RemoteClass remoteClass = CreateRemoteClass();
remoteClass.NotificationEvent +=
    new NotificationEventDelegate(remoteClass_NotificationEventHandler);

 
Using the wrapper your client side code will look something like this:

// Call local method to create class using Activator.GetObject()
RemoteClass remoteClass = CreateRemoteClass();
// Create local instance of the event wrapper
RemoteEventWrapper wrapper = new RemoteEventWrapper();
remoteClass.NotificationEvent +=
    new NotificationEventDelegate(wrapper.NotificationEventHandler);
wrapper.NotificationEvent +=
    new NotificationEventDelegate(wrapper_NotificationEventhandler);

 
If you step through the code you’ll see that the NotificationEvent in the RemoteClass executes on the server (it is a remoted class after all) but the NotificationEvent in the RemoteEventWrapper executes on the client.

Full serialization needed

If you’re reusing some remoting code from a previous project then you may find, as I did, that it’s OK for handling scenarios where the client is calling the server synchronously. But to handle events raised on the server and passed to the client you may need to make some changes. I experienced exceptions on the server when it tried to marshall the event back to the client. The problem is that the client needs to create a channel with appropriate serialization options. My original code to create and register the channel on the client was simply this:

HttpChannel channel = new HttpChannel();
ChannelServices.RegisterChannel(channel, false);

 
But you need to provide a server channel that implements full serialization. The chances are that the code in your server already does exactly what’s needed and just needs a few minor tweaks to implement this on the client:

Hashtable properties = new System.Collections.Hashtable();
properties.Add("port", 0);
SoapServerFormatterSinkProvider serverProvider = new SoapServerFormatterSinkProvider();
serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
SoapClientFormatterSinkProvider clientProvider = new SoapClientFormatterSinkProvider();
HttpChannel channel = new HttpChannel(properties, clientProvider, serverProvider);
ChannelServices.RegisterChannel(channel, false);

 
Note that the port specified in the second line is 0. This is the port number on the client for the server to call back on. Specifying 0 causes the framework to use the first available port.

SingleCall or Singleton ?

My remoting code created SingleCall objects. That’s fine for a lot of remoting implementations but in this scenario we want the remote instance to remain instantiated so when you call the RegisterWellKnownServiceType()  you need to specify WellKnownObjectMode.Singleton.

Tracking instances

The next problem is how does your server raise an event on the remoted class instances created by clients ?

There might be a more elegant way of doing this but I created a static class containing a list of instances. The remoted class’s constructor needs to call a method on the tracking class to get itself added to the list. I called mine RegisterInstance().

The tracker class implements a method that iterates through the list and raises the event on each one.

LifetimeService

Now that we have a Singleton object we have another problem. By default, the remote class instance created on the server will be garbage collected after 5 minutes. We need to prevent this. The quick solution is to override InitializeLifetimeService() and return null:

public override Object InitializeLifetimeService()
{
    return null;
}

 
Because the wrapper class is marshalled to the server, it will also be garbage collected after 5 minutes by default so it also needs the same override to InitializeLifetimeService.

We’re nearly there, there’s just one more problem to be solved…

Invoking event delegates

At this point, if you try out your code it should work. Until you exit the client at which point the server will encounter an exception when the next event is raised. The problem is that the delegate it’s trying to invoke no longer exists. 

Normally we’d invoke the delegate methods by simply checking that the event wasn’t null:

internal void RaiseEvent(EventArgs e)
{
    if (NotificationEvent != null)
    {
        NotificationEvent(this, e);
    }
}

 
We need something a bit more sophisticated to iterate through the delegates and call each one. If we get an exception we need to handle it and remove the delegate from the list as it no longer exists:

internal void RaiseEvent(EventArgs e)
{
    if (NotificationEvent != null)
    {
        Delegate[] notificationEventDelegates = NotificationEvent.GetInvocationList();
        lock (this)
        {
            foreach (NotificationEventDelegate notificationEventDelegate in notificationEventDelegates)
            {
                try
                {
                    notificationEventDelegate.Invoke(this, e);
                }
                catch (Exception)
                {
                    // Remove expired delegate.
                    NotificationEvent -= notificationEventDelegate;
                }
            }
        }
    }
}

 
That’s it!

Advertisements

Written by Sea Monkey

March 2, 2010 at 9:00 pm

Posted in Development

Tagged with ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: