Nov 17, 2009

Decoupled ViewModel Messaging (Part 1)

 

NOTE: All the code shown in this series has been published to codeplex. If you would like to download the framework or the sample application that uses the framework you can find it here: http://courier.codeplex.com/

Lately I have been playing around with different ways to decouple the communication between ViewModels in the MVVM pattern. My focus has been in WPF, as this is what the MVVM pattern is suited for however, this messaging technique is not specific to WPF and could be used in any other decoupled solution (MVC, MVP etc..)

I poked around on the tubes looking for examples of how other people have solved this problem and there are many different options out there. My implementation borrows heavily from the CoreMVVM framework on codeplex which itself borrows bits from Cinch, Prism, MVVM Foundation etc.. 

My first goal was to really just get down in the weeds and understand how these other frameworks were handling decoupled messaging between objects. Once I wrapped my head around it I then came up with two features that I thought would be nice to have in a messaging solution that I hadn’t seen in other frameworks.

The ability to manually unsubscribe from messages and the ability cache messages for re-broadcast.

Before I dive in and talk about the new features I wanted to add let me go over the basic implementation of the Mediator pattern I did. Again, this is very similar to how the CoreMVVM framework does it, so if you are familiar with that framework this should review.

The basis of the implementation is a basic mediator pattern. You have a mediator object that stores a mapping of subscribers and messages. This gives the mediator enough information to understand what messages to dispatch to what subscribers. The mediator in my case looks like this 

//Method bodies elided for clarity
public class Mediator
{
private readonly MessageToSubscriberMap subscribers = new MessageToSubscriberMap();
private readonly List<CachedMessage> cachedMessages = new List<CachedMessage>();

public void RegisterForMessage(String message, Delegate callback)
{
...
}

public void UnRegisterForMessage(String message, Delegate callback)
{
...
}

public void BroadcastMessage<T>(String message, Boolean cacheMessage, T parameter)
{
...
}

public void BrodcastMessage<T>(String message, Boolean cacheMessage)
{
...
}

private void GetMessagesFromCache(String message, Delegate callback)
{
...
}
}


From the snippet above you can see there isn’t much going on in the Mediator. The key is really the MessageToSubscriberMap object defined at the top. This object is what stores the (weak) reference to the subscriber (in this case a delegate to be invoked) and the message that subscriber is interested in.



Here are the guts of the MessageToSubscriberMap class



//Method bodies elided for clarity
internal class MessageToSubscriberMap
{
//Store mappings with weak references to prevent leaks
private readonly Dictionary<String, List<WeakSubscriber>> map = new Dictionary<String, List<WeakSubscriber>>();
internal void AddSubscriber(String message, Object target, MethodInfo method, Type subscriberType)
{
...
}

internal void RemoveSubscriber(String message, Delegate callback)
{
...
}

internal List<Delegate> GetSubscribers(String message)
{
...
}
}


The key piece of this map is the private Dictionary field. This is the mapping of Messages to the collection of WeakSubscribers that are listening for that message. I have called the object WeakSubscriber to indicate that a subscriber is really a WeakReference to a delegate. Here is the complete implementation of the WeakSubscriber class



internal class WeakSubscriber
{
private readonly MethodInfo method;

public MethodInfo Method { get { return method; } }

private readonly Type delegateType;
private readonly WeakReference weakReference;

internal WeakSubscriber(Object target, MethodInfo method, Type parameterType)
{
//create a WeakReference to store the instance of the target in which the Method resides
weakReference = new WeakReference(target);
this.method = method;
delegateType = parameterType == null ? typeof(Action) : typeof(Action<>).MakeGenericType(parameterType);
}

internal Delegate CreateAction()
{
Object target = weakReference.Target;
return target != null ? Delegate.CreateDelegate(delegateType,weakReference.Target,method) : null;
}

public Boolean IsAlive
{
get { return weakReference.IsAlive; }
}

}


Ok so now we have seen the extent of my little messaging framework. Again, most of this code is very similar to the CoreMVVM framework implementation of the mediator pattern so, for users of that framework, this should be familiar.



I am going to wrap this post up with a quick example of how you would subscribe to a message and how you would broadcast a message with this framework.



To broadcast a message you would do this:



Mediator.BroadcastMessage("Content1Message",true, messageContent);


Note the second parameter in this method call is for caching the message which I will explain in a following post.



If you wanted to subscribe to this Content1Message you would register for the message like this:



Mediator.RegisterForMessage("Content1Message", (Action<String>)OnContent1MessageReceived);


In the next post I am going to describe the two new features I added to the framework which are the ability to unsubscribe from a message and the ability to cache messages for re-broadcast.



This project was built in Visual Studio 2010 Beta 2, If you haven’t already I highly recommend downloading VS2010



As a side note, the specific reason I using VS2010 is because I plan on parallelizing the dispatching of messages to subscribers in the future and want to use the new parallel framework in .NET 4.0 



NOTE: All the code shown in this series has been published to codeplex. If you would like to download the framework or the sample application that uses the framework you can find it here: http://courier.codeplex.com/

Labels: , , , , ,