Skip to main content

Run your own Message Bus to communicate between View, View models & Services

I am working on a project that had a requirement to centralize message system. I decided to use Prism’s Event Aggregator which worked fine. However I did find a few difficulties in using it and created my own Message Bus.

Shown below is a simple interface and implementation of a message bus, this service would normally be a singleton instance shared among all view models and other services.

We have to create an event class and event Args class for every message which we want to publish.

public class NotificationEventArgs: EventArgs

{

    public NotificationEventArgs() {}

    public NotificationEventArgs(string message) {
        Message = message;
    }

    public string Message {
        get;
        protected set;
    }

}

Take a look at the method signatures that I had in mind. A Message Bus should provide functionality to RegisterToReceiveMessages to a message, UnregisterToReceiveMessages from a message and SendMessage a message with a specific token.

public interface IMessageBusService

{

    void SendMessage(string token, NotificationEventArgs e);

    void RegisterToReceiveMessages(string token, EventHandler < NotificationEventArgs > callback);

    void UnregisterToReceiveMessages(string token, EventHandler < NotificationEventArgs > callback);

}



public class MessageBusService: IMessageBusService

{

    private readonly object sharedLock = new object();

    private readonly Dictionary < string, List < Handler >> subscriptions = new Dictionary < string, List < Handler >> ();

    ;

    public void SendMessage(string token, NotificationEventArgs e)

    {

        // Send notification through the MessageBus

        MessageBus.Default.Notify(token, this, e);

    }

    public void RegisterToReceiveMessages(string token, EventHandler < NotificationEventArgs > callback)

    {

        // If already registered, add callback to the list

        lock(sharedLock)

        {

            if (subscriptions.ContainsKey(token))

            {

                subscriptions[token].Add(new Handler(callback, eventArgsType));

            }

            // Otherwise create an entry with a new callback list
            else

            {

                subscriptions.Add(token, new List < Handler > {
                    new Handler(callback, eventArgsType)
                });

            }

        }

        MessageBus.Default.Register(token, messageBusHelper);

    }

    public void UnregisterToReceiveMessages(string token, EventHandler < NotificationEventArgs > callback)

    {

        lock(sharedLock)

        {

            if (subscriptions.ContainsKey(token))

            {

                // Get handler with the same type

                Handler handler = subscriptions[token].Where(h => h.EventArgsType == eventArgsType).SingleOrDefault();



                // If registered, remove callback from the list

                if (handler != null) subscriptions[token].Remove(handler);



                // Remove dictionary entry if no subscibers left

                if (subscriptions[token].Count == 0) subscriptions.Remove(token);

            }

        }

    }
    MessageBus.Default.Unregister(token, messageBusHelper);

}

Message Bus facilitates communication among view-models and prevent the memory leaks by using weak references.

public sealed class MessageBus

{

    // Provide thread-safe access to subscriptions

    private readonly object sharedLock = new object();

    // Each token can have multiple subscriptions

    private readonly Dictionary < string, List < WeakReference >> subscriptions = new Dictionary < string, List < WeakReference >> ();

    private static readonly object staticLock = new object();

    private static MessageBus _instance;

    private MessageBus() {}

    public static MessageBus Default

    {

        get

        {

            lock(staticLock)

            {

                if (_instance == null)

                {

                    _instance = new MessageBus();

                }

                return _instance;

            }

        }

    }

Register subscriber using a string token, which is usually defined as a constant and Subscriber performs internal notifications.

public void Register(string token, INotifyable subscriber)

{

lock(sharedLock)

{

// Add token if not present

if (!subscriptions.ContainsKey(token))

{

subscriptions.Add(token, new List < WeakReference > {
new WeakReference(subscriber)
});

return;

}

// Get subscribers for this token

List < WeakReference > weakSubscribers;

if (subscriptions.TryGetValue(token, out weakSubscribers))

{

// See if subcriber is already present

WeakReference existing =

(from w in weakSubscribers where w != null && w.IsAlive && ReferenceEquals(w.Target, subscriber) select w)

.SingleOrDefault();

// Add if subcriber is already present

if (existing == null)

subscriptions[token].Add(new WeakReference(subscriber));

}

}

}

Remove subscriber from the invocation list

public void Unregister(string token, INotifyable subscriber)

{

lock(sharedLock)

{

List < WeakReference > weakSubscribers;

if (subscriptions.TryGetValue(token, out weakSubscribers))

{

// Find subscriber

WeakReference weakSubscriber =

weakSubscribers.Where(w = > w.IsAlive && ReferenceEquals(w.Target, subscriber)).SingleOrDefault();
 

// Remove subscriber

if (weakSubscriber != null)

weakSubscribers.Remove(weakSubscriber);
 

// Remove dictionary entry if no subscibers left

if (subscriptions[token].Count == 0) subscriptions.Remove(token);

}

}

}

}

The idea is Reciever viewmodel will get notification on SenderViewmodel is loaded and Reciever viewmodel will now have required user information.

Sender viewmodel will send a message from with a specific message token as “UserInfoLoaded”.

public partial class SenderViewModel

{

public SenderViewModel()

{

messageBusService = ServiceLocator.Current.GetInstance < IMessageBusService >
();

messageBusService.SendMessage(“UserInfoLoaded”, new NotificationEventArgs());

}

}

Any viewmodel in which there is RegisterToReceiveMessages is with “UserInfoLoaded” would receive notification when the message was sent.

public partial class RecieverViewModel

{

public RecieverViewModel()

{

messageBusService = ServiceLocator.Current.GetInstance < IMessageBusService >
();

messageBusService.RegisterToReceiveMessages(“UserInfoLoaded”, OnUserInfoLoaded);

}

private void OnUserInfoLoaded(object sender, NotificationEventArgs e)

{

//Can read userinfo needed

}

}
Charan Mullakuru

Charan Mullakuru

Charan is a Senior Software Engineer with Trigent Software. He has over eight years of experience in Microsoft .Net technologies. In his leisure time, he likes to play cricket, listen to music and watch movies.