Events and Delegates in Remoting

This article has been excerpted from book "The Complete Visual C# Programmer's Guide" from the Authors of C# Corner.

Event notification in the remoting framework employs the same paradigm as the rest of the .NET Framework: delegates and events. There are minor differences in construction; and we should point out a constraint before proceeding.

The event originator cannot be registered as a server-activated object in SingleCall mode. Because the object is re-created with each method call, it won't be able to retain a reference to the event's delegate.

The projects are in the Sample3 folder and consist of SimpleEventLib.dll and two console applications, EventServer and EventClient. The delegate, with its method signature, and the event arguments class code are shown in Listing 25.11.

Listing 25.11:RemoteArgs.cs

        public delegate void RemoteEventHandler(
object sender, RemoteArgs args);
        [Serializable]
        public class RemoteArgs
        {
            private string machineName = null;
            private string currentDir = null;
            internal RemoteArgs()
            {
                machineName = Environment.MachineName;
                currentDir = Environment.CurrentDirectory;
            }

            public string MachineName
            {
                get
                {
                    return (machineName);
                }
            }

            public string CurrentDir
            {
                get
                {
                    return (currentDir);
                }
            }
        }


The delegate should come as no surprise; it looks like any other event delegate. The RemoteArgs class doesn't derive from System.EventArgs. To compensate for the lack of an EventArgs parent, the [Serializable] attribute has been attached so it can be marshaled across application domains. The event sink, Listing 25.12, derives from MarshalByRefObject and implements an event handler.

Listing 25.12: SyncRemoteEvent.cs


        public class SyncRemoteEvent : MarshalByRefObject
        {
            public SyncRemoteEvent()
                : base()
            {
                Console.WriteLine("In SyncRemoteEvent constructor");
            }

            public virtual void EventHandler(object send, RemoteArgs arg)
            {
                Console.WriteLine();
                Console.WriteLine("In SyncRemoteEvent.EventHandler");
                Console.WriteLine("Machine:{0} Dir:{1}",
                args.MachineName, args.CurrentDir);
                Console.WriteLine();
            }
        }


SimpleObject has changed very little, except that we have added an event delegate, Listing 25.13.

Listing 25.13: SimpleObject.cs


        public class SimpleObject : MarshalByRefObject
        {
        public event RemoteEventHandler RemoteEvent;
        .
        .
        .


In the ConcatString() we insert the event-handling code shown in Listing 25.14 to test whether any remote objects are subscribing to the event. If a subscriber exists, a RemoteArgs instance is created and the event handler is called.

Listing 25.14: SimpleObject.cs

        public string ConCatString(string first, string second)
        {
            string concat = null;
            Console.WriteLine("In SimpleObject.ConCatString method.");

            if (RemoteEvent != null)
            {
                RemoteArgs args = null;
                args = new RemoteArgs();
                RemoteEvent(this, args);
            }

            concat = first + " " + second;
            return (concat);
        }


The server creates an HttpChannel to send and receive messages. It registers both server-activated and client-activated SimpleObjects and waits for a client to call a method on the remote object. (See Listing 25.15.)

Listing 25.15: EventServer.cs

using
System;
using
System.Runtime.Remoting;
using
System.Runtime.Remoting.Channels;
using
System.Runtime.Remoting.Channels.Http;
using
SimpleEventLib;

namespace
EventServer
{
    class ServerEvent
    {
        static void Main(string[] args)
        {
            HttpChannel http = null;
            http = new HttpChannel(1234);
            ChannelServices.RegisterChannel(http);
            RemotingConfiguration.RegisterWellKnownServiceType(
            typeof(SimpleObject),
            "Simple",
            WellKnownObjectMode.Singleton);
            RemotingConfiguration.ApplicationName = "Simple";
            RemotingConfiguration.RegisterActivatedServiceType(
            typeof(SimpleObject));
            Console.WriteLine("Press <enter> to exit.");
            Console.ReadLine();
        }
    }
}


Notice that the server-activated registration uses Singleton mode. There is no registration of the SyncRemoteEvent object. Running the server executable produces the output in Figure 25.10, while Figure 25.11 displays the client's output.

Figure-25.10.jpg

Figure 25.10: EventServer

Figure-25.11.jpg

Figure 25.11: EventClient Output

Listing 25.16: EventClient.cs


        class ClientEvent
        {
            public ClientEvent()
            {
                Console.WriteLine("In ClientEvent constructor");
            }

            static int Main(string[] args)
            {
                SyncRemoteEvent client = null;
                HttpChannel http = null;
                http = new HttpChannel(2345);
                ChannelServices.RegisterChannel(http);
                //client = (SyncRemoteEvent)ClientEvent.InitClientEvent();
                client = new SyncRemoteEvent();
                SimpleObject simple = null;
                simple = (SimpleObject)Activator.GetObject(
                typeof(SimpleObject),
                "http://localhost:1234/Simple");
                simple.RemoteEvent +=
                new RemoteEventHandler(client.EventHandler);
                string ret = null;
                ret = simple.ConCatString("using", "Activator.GetObject");
                Console.WriteLine(ret);
                simple.RemoteEvent -=
                new RemoteEventHandler(client.EventHandler);
                ret = simple.ConCatString("2 using", "Activator.GetObject");
                Console.WriteLine(ret);
                return (0);
            }
        }


The client creates a channel, but in this instance it specifies a port number (see Listing 25.16). The server must have a defined port number to contact the client during the event firing. The event handler is created with a simple new; no registration is required. The line commented out above the new operator will be discussed later. SimpleObject is a server-activated object because of the Activator.GetObject(). The code for a client-activated remote object could be Activator.CreateInstance() or as follows:


                RemotingConfiguration.RegisterActivatedClientType(
typeof(SimpleObject),
                "http://localhost:1234/Simple");
                simple = new SimpleObject();


The client then subscribes to the event and calls SimpleObject.ConcatString(); it then unsubscribes and makes a subsequent call to ConcatString().

While experimenting with remote events, we made an interesting discovery. As the code stands now, the event is fired from within the server's application domain to the client's. The client doesn't actually intercept the event unless the server has some reference to it. If, for example, ClientEvent was derived from SyncRemoteEvent, an exception would be thrown when it is registered with SimpleObject's event delegate-that is, unless the remoting framework is tricked into thinking it is dealing with a SyncRemoteEvent rather than a ClientEvent object. Deceiving the framework results in output such as that shown in Figure 25.12.

Figure-25.12.jpg

Figure 25.12: Event Client Output

Listing 25.17: EventClient.cs


        class ClientEvent : SyncRemoteEvent
        {
            public ClientEvent()
                : base()
            {
                Console.WriteLine("In ClientEvent constructor");
            }

            private static object InitClientEvent()
            {
                SyncRemoteEvent client = null;
                RemotingConfiguration.RegisterWellKnownServiceType(
                typeof(ClientEvent),
                "Event",
                WellKnownObjectMode.SingleCall);
                client = (SyncRemoteEvent)Activator.GetObject(
                typeof(SyncRemoteEvent),
                "http://localhost:2345/Event");
                return ((object)client);
            }

            public override void EventHandler(object sender,
            RemoteArgs args)
            {
                Console.WriteLine();
                Console.WriteLine("In ClientEvent.EventHandler");
                Console.WriteLine("Machine:{0} Dir:{1}",
                args.MachineName, args.CurrentDir);
                Console.WriteLine();
                base.EventHandler(sender, args);
            }
        }


The first step in accomplishing direct event interception is to make the EventHandler() in SyncRemoteEvent a virtual method. Then, override the method in ClientEvent. Duping the framework takes place in the static method InitClientEvent(). Register the server-activated object as a type of ClientEvent in either SingleCall or Singleton mode. Then, Activator.GetObject() returns a transparent proxy of the requested type, SyncRemoteEvent. If you look at the output, a ClientEvent instance is created, but the proxy has been downcast to a SyncRemoteEvent instance. The result is that the server doesn't have any reference to ClientEvent. No exception is thrown during registration with the event delegate. Because the EventHandler() is virtual, the proper method is called. Experimenting with this as a client-activated object resulted in failure, no matter what twists and contortions were taken.

Conclusion

Hope this article would have helped you in understanding Events and Delegates in Remoting. See other articles on the website on .NET and C#.

visual C-sharp.jpg The Complete Visual C# Programmer's Guide covers most of the major components that make up C# and the .net environment. The book is geared toward the intermediate programmer, but contains enough material to satisfy the advanced developer.


Similar Articles