Working with Events/Callbacks in WCF — a simplified beginner example.

In this example below, when the Service receives an Event, it sends notifications to only those subscribers who are interested in that particular event. Long ago, I had worked on a Windows Service which would poll a certain folder at a predetermined interval, and if it found a excel/csv file, read that file , update DB & finally delete that file from that path. My initial thought was, how WCF can replace or improve that whole process which currently Windows Service was doing. Finally, I ended up with this imaginary scenario, to help understand events/callbacks in WCF.This example below has 3 major players. The WCF Service, A Windows Form application acting as the Event generator and another Windows application working as client. To have multiple client instances, in my Client Windows Form application, I did “Solution Explorer->Debug->Start New Instance”, which effectively starts a new application instance/brand new process.Now the functionality part —
The Clients:-
Basically each client can tell the Service, send me a notification, when a certain filetype-based-event shows up. Say I have Client1, subscribing to the service — sending “.xls” as one of the parameter — which effectively means if the
service gets an event of fileType ‘.xls’ , let me know the filename. Similarly Client2 can also subscribe for .xls or any other file type say .doc or .txt. Like this, I generate many client instances, subscribed for different filetypes.
The Event Generator:-
All it does is, it sends a filename along with extension to the server. So this Windows Form application has just a TextBox, to enter filename & a button to invoke Web Service, passing the filename and fileType as arguments.
The WCF Service:-
The service uses netTCPBinding, since everything internal and .net. Only netTCPBinding and wsDualHttpBinding supports session and hence callbacks. When a client subscribes, the service adds the client to the list along with 2 pieces of
client information — SubscriberID and FileType, the subscriber is interested in.So when a event comes in, it first checks if there is any subscriber, who is interested in that particular filetype — if it really finds any such subcriber, then next it checks if it is still alive/connected and if yes, then the service issues a callback, sending the filename.
Here’s how the complete thing, in action, looks like.

img4

img1

img3

//The WCF Service code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace WCFNotificationService
{
    [ServiceContract(CallbackContract = typeof(IEventNotificationCallback), SessionMode = SessionMode.Required)]
    public interface IEventNotification
    {
        [OperationContract(IsOneWay = true)]
        void ReceiveFile(string strFileType, string strFilename);

        [OperationContract(IsOneWay = true)]
        void Subscribe(Guid SubID, string FileTypeInterested);

        [OperationContract(IsOneWay = true)]
        void UnSubscribe(Guid SubID);
    }

    public interface IEventNotificationCallback
    {
        [OperationContract(IsOneWay = true)]
        void OnReceiveFile(string strFilename);
    }

    public class EachSubscriber
    {
        [DataMember]
        public Guid SubID { get; set; }
        public string FileTypeInterested { get; set; }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace WCFNotificationService
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class EventNotification : IEventNotification
    {
        private object locker = new object();
        private Dictionary<EachSubscriber, IEventNotificationCallback> Subscribers = new Dictionary<EachSubscriber, IEventNotificationCallback>();

        //This will be called by the Event Generator app.
        public void ReceiveFile(string strFileType, string strFilename)
        {
            //get all the subscribers
            var subscriberKeys = (from c in Subscribers
                         select c.Key).ToList();
            
            subscriberKeys.ForEach(delegate(EachSubscriber c1)
            {
                IEventNotificationCallback callback = Subscribers[c1];
                if (((ICommunicationObject)callback).State == CommunicationState.Opened)
                {
                    //call back only those subscribers who are interested in this fileType
                    if (c1.FileTypeInterested.ToLower() == strFileType.ToLower())
                    {
                        callback.OnReceiveFile(strFilename);
                    }
                }
                else
                {
                    //These subscribers are no longer active. Delete them from subscriber list
                    subscriberKeys.Remove(c1);
                    Subscribers.Remove(c1);
                }  
            });
        }

        public void Subscribe(Guid SubID, string FileTypeInterested)
        {
            try
            {
                IEventNotificationCallback callback = OperationContext.Current.GetCallbackChannel<IEventNotificationCallback>();
                lock (locker)
                {
                    EachSubscriber _EachSubscriber = new EachSubscriber();
                    _EachSubscriber.SubID = SubID;
                    _EachSubscriber.FileTypeInterested = FileTypeInterested;
                    Subscribers.Add(_EachSubscriber, callback);
                }
            }
            catch
            {
            }
        }

        public void UnSubscribe(Guid SubID)
        {
            try
            {
                lock (locker)
                {
                    var SubToBeDeleted = from c in Subscribers.Keys
                                where c.SubID == SubID
                                select c;
                    if (SubToBeDeleted.Count() > 0)
                    {
                        Subscribers.Remove(SubToBeDeleted.First());
                    }
                }
            }
            catch
            {
            }
        }


    }
}

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="false" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="WCFNotificationService.EventNotification">
        <endpoint address="net.tcp://localhost:8733/Design_Time_Addresses/WCFNotificationService/EventNotification/" binding="netTcpBinding" contract="WCFNotificationService.IEventNotification">
          <identity>
            <dns value="localhost" />
          </identity>
        </endpoint>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8732/Design_Time_Addresses/WCFNotificationService/EventNotification/" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>

//The Client

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        private ServiceReference1.EventNotificationClient _client;
        private Guid _Guid = Guid.NewGuid();
        static Label l2 = new Label();

        public Form1()
        {
            InitializeComponent();
            InstanceContext context = new InstanceContext(new FileTypeCallback());
            _client = new ServiceReference1.EventNotificationClient(context);
        }

        public class FileTypeCallback : ServiceReference1.IEventNotificationCallback
        {
            public void OnReceiveFile(string strFilename)
            {
                l2.Text = "File received: " + strFilename;
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            l2.Text = "";
            if (_client.State == CommunicationState.Opened)
            {
                label2.Text = "Please unsubscribe first & then subscribe for a different file type. ";
                return;
            }
            try
            {
                if (_client.State == CommunicationState.Created)
                {
                    _client.Subscribe(_Guid, comboBox1.SelectedItem.ToString());
                }
                else
                {
                    InstanceContext context = new InstanceContext(new FileTypeCallback());
                    _client = new ServiceReference1.EventNotificationClient(context);
                    _client.Subscribe(_Guid, comboBox1.SelectedItem.ToString());
                }
            }
            catch (Exception e1)
            {
                throw new Exception("Could not subscribe");
            }
            label2.Text = "Subscribed for FileType:-  " + comboBox1.SelectedItem;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            l2.Text = "";
            if (_client.State == CommunicationState.Opened)
            {
                _client.UnSubscribe(_Guid);
                _client.Close();
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_client.State == CommunicationState.Opened)
            {
                _client.UnSubscribe(_Guid);
            }
        }

        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            label2.Text = "FileType Selected:-  " + comboBox1.SelectedItem;
        }

        private void Form1_Load_1(object sender, EventArgs e)
        {
            l2.Width = 800;
            l2.ForeColor = Color.Red;
            panel1.Controls.Add(l2);
        }
    }
}

//The Event Generator

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;

namespace WCFEventGenerator
{
    public partial class Form1 : Form
    {
        private ServiceReference1.EventNotificationClient _client;
        public Form1()
        {
            InitializeComponent();
        }

        public class FileTypeCallback : ServiceReference1.IEventNotificationCallback
        {
            public void OnReceiveFile(string strFilename)
            {
                //Won't implement anything here
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            InstanceContext context = new InstanceContext(new FileTypeCallback());
            _client = new ServiceReference1.EventNotificationClient(context);

            if (_client.State != CommunicationState.Opened)
            {
                _client.ReceiveFile(textBox1.Text.Split('.')[1], textBox1.Text);
            }
        }

        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            label2.Text = "Event generator file name:-  " + textBox1.Text;
        }
    }
}

 

EventGen


Hope this helps. Comments/suggestions welcome. Thanks for reading.

Advertisements
This entry was posted in WCF. Bookmark the permalink.

One Response to Working with Events/Callbacks in WCF — a simplified beginner example.

  1. This is wonderful! I used this, it works! Ran into your post while searching for examples for doing events / callbacks with WCF. This is a very concise example, just what I needed. Changed it a little to deal with only strings instead of files, but that was no big deal. Saved me hours of work!

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