TCP Eventing [Utkarsh Shah]

Eventing is reliable in case of named pipe scenarios on same machine but not over network. So if underlying sockets close connection eventing won’t try to establish it for you. Using events over network is not recommended. But if you still want to use it, this post takes us over a simple client server example and uses TCPChannel for communication. Here is how Service interface and actual service look like. We have property Name and an event NameChanged. Idea is that server and client interact with NameChange event.

Service Interface
    public interface IService
    {
        string Name { get; set;}
        event EventHandler NameChanged;
    }

EventShim class
/// <remark>
/// See the following KB article for an explanation of why this class is needed
/// support.microsoft.com/default.aspx?scid=kb;en-us;312114
/// </remark>
public sealed class EventShim : MarshalByRefObject
{
public EventShim(IService serviceProxy)
{
serviceProxy.NameChanged += this.ServerNameChanged;
}
public event EventHandler NameChanged;
[OneWay]
public void ServerNameChanged(object sender, EventArgs e)
{
if (NameChanged != null)
NameChanged.Invoke(sender, e);
}
}

Service
    class Service : MarshalByRefObject, IService
    {
        string name = "John Doe";
        public string Name
        {
            get
            {
                return this.name;
            }
            set
            {
                this.name = value;
                Console.WriteLine("Name Changed by remote client to {0}", this.name);
                if (NameChanged != null)
                {
                    NameChanged.BeginInvoke(this, null, null, null);
                }
            }
        }
        public event EventHandler NameChanged;
    }

Code for Service Host

Create a TCPChannel that uses server and client sink providers. We set TypeFilterLevel to Full so that we can pass this event shim object around.

BinaryServerFormatterSinkProvider serverProv = new BinaryServerFormatterSinkProvider();
serverProv.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
BinaryClientFormatterSinkProvider clientProv = new BinaryClientFormatterSinkProvider();
IDictionary props = new Hashtable();
props["port"] = 8000;
// Create and register an TCP channel
TcpChannel serviceChannel = new TcpChannel(props, clientProv, serverProv);
ChannelServices.RegisterChannel(serviceChannel, true);
// Expose an object
Service service = new Service();
RemotingServices.Marshal(service, "service.rem", typeof(IService));

Code for Client
Client retrieves service proxy and ties up with NameChanged event for that service using EventShim. It uses different port than server to create its TCPChannel as same port can’t be shared by multiple processes.

    public partial class TCPClientForm : Form
    {
        IService service;
        EventShim eventShim;

        public TCPClientForm()
        {
            // Create the outgoing and incoming channels
           BinaryServerFormatterSinkProvider serverProv = new BinaryServerFormatterSinkProvider();
           serverProv.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
           BinaryClientFormatterSinkProvider clientProv = new BinaryClientFormatterSinkProvider();
            IDictionary props = new Hashtable();
            props["port"] = 8001;
            TcpChannel channel = new TcpChannel(props, clientProv, serverProv);
            ChannelServices.RegisterChannel(channel, true);
            // Get the service proxy
            this.service = (IService)Activator.GetObject(typeof(IService), "tcp://localhost:8000/service.rem");
            this.serverNameDisplay.Text = service.Name;
            eventShim = new EventShim(service);
            eventShim.NameChanged += new EventHandler(service_NameChanged);
        }

        void service_NameChanged(object sender, EventArgs e)
        {
            BeginInvoke((MethodInvoker)delegate()
            {
                this.serverNameDisplay.Text = service.Name;
            });
        }
   }

Note:
Above EventShim expires in 5 minutes. So if you want it to last more, you should implement InitializeLifetimeservice.