M-V-VM – Coming together with the Mediator Pattern

Posted Monday, July 12, 2010 4:46 PM by Nathan Zaugg

CommunicationThe Model-View-ViewModel (M-V-VM or MVVM) pattern has been widely adopted by those who build WPF and Silverlight applications.  WPF and Silverlight have such strength in data binding that there is no real need to have any code-behind in a view.  It is sometimes difficult to get to this point but once you get there the application becomes much more maintainable, and testable. 

This is because a clear separation of concerns where the View’s responsibility (concern) is to display UX controls that are bound to real data.  The ViewModel is to represent the data in a way that the View can bind to it and to allow a way to produce a model object as a result.  The Model is responsible for validating data and is used to persist back to the database. 

While this greatly simplifies a single view, in practice many views/view models need to work together.  The controller is supposed to help provide some of this cross-view-model communication.  It’s actually a myth that MVVM architecture does not include (or need) a controller.  At some point there has to be an object that creates a view-model and opens up a view and by definition that object is the controller.

MVC Pattern

MVP Pattern

MVVM Pattern

One simple answer to the problem of communication between different parts of the application is to use the Mediator Pattern.  I am by no means the first to suggest the mediator pattern as a possible solution to this problem.  Josh Smith, among others, suggested it in a past blog post. The implementation I built is a little unique in that it does not make use of a string or an enum to determine message type. 

This implementation allows:

  1. Views to communicate with the controller in a static-typed way.
  2. Views to communicate with other views in a static-typed way.
  3. Messages to be sent to all ICommunication classes that are listening for a specific message type.
  4. Messages to be sent to all ICommunication classes based on the host classes type.
  5. Messages to be sent to a specific object (callback).

CS Mediator

But we’ll get to that, lets start at the beginning. First, each class that wishes to participate in communication (ViewModels and the Controller) must impelment the ICommunication object.

public interface ICommunication
{
void ReceiveMessage(ICommunication sender, Message message);
}

The reason all participants must implement this is because we want to make it possible for some ViewModelBase to implement the stuff that is shared between all view models.  It is also a “catch all” for messages that do not apply to a better action.

This implementation of the Mediator pattern is based on types.  This means that the type of message we send determines how the message is routed.  For example, consider the following Message types.

public abstract class Message { }

public abstract class DataMessage : Message { }

public abstract class ActionMessage : Message { }

public abstract class StatusMessage : Message { }

public class StatusUpdateMessage : StatusMessage
{
public string ProgressAction { get; set; }
public string ProgressText { get; set; }

/// <summary>
/// The value to set the progress bar.
/// A negative value hides the progress bar
/// </summary>
public sbyte ProgressBarValue { get; set; }
}

public class CloseWindowActionMessage : ActionMessage
{
public WorkspaceViewModelBase Window { get; set; }
public CloseWindowActionMessage(WorkspaceViewModelBase value)
{
Window = value;
}
}

public class OpenWindowMessage : ActionMessage
{
public string ViewName { get; set; }
}

public class OpenWindowMessage<T> : OpenWindowMessage
{
public T WindowParameter { get; set; }
}

You can see that they all derive from the abstract base class Message, but there are different taxonomies for different kinds of messages.  For example, if I want to open a new window and I want to pass a parameter to that window to open it, the type I would use is OpenWindowMessage<Fruit> which derives from OpenWindowMessage.  OpenWindowMessage contains the name of the window we want to open.  OpenWindowMessage is also an ActionMessage which tells the controller it wants it to do something. ActionMessage simply derives from Message.

Therefore to send a message to open a FruitWindowView with a Banana instance we would call:

// Send a message to open up a new view
Controller.SendMessage<OpenWindowMessage<Banana>>(this,
new OpenWindowMessage<Banana>() { ViewName = "Edit Fruit", WindowParameter = SelectedBanana });

The Controller will receive an OpenWindow message and act accordingly.  In fact, at this point you may be curious how a subscriber requests these messages.  Here is a excerpt from the Controller:

// Setup Messaging
Controller.Subscribe<ActionMessage>(this, (MessageDelegate<ActionMessage>)((sender, message) =>
{
if ( message is CloseWindowActionMessage )
Workspaces.Remove(((CloseWindowActionMessage)message).Window);

if (message is OpenWindowMessage)
{
OpenWindowMessage openMessage = message as OpenWindowMessage;
if (openMessage.ViewName == "Edit Fruit")
{
AddJobRun(((OpenWindowMessage<Fruit>)openMessage).WindowParameter);
}
}
}));

You can see in this example that the controller is opting to subscribe to any messages that are ActionMessage.  This includes messages that are ActionMessage or OpenWindowMessage or CloseWindowMessage, as they inherit from ActionMessage.  You can subscribe to messages at any level.  If we wanted specific implementation to handle OpenWindow<Peanuts> we can register a message handler to execute specific code when that message is received.  The great part is because this is all static-typed our message handler gets full use of code completion features and we do not have to cast our object.

You can start to see that it is going to be very important to keep the types of messages clear, clean, and concise.  That’s why I didn’t create an OpenFruitWindowMessage, because an OpenWindowMessage<T> should work for any window that requires a parameter.

Here is the interface for IMediator, which is generally implemented by a Mediator class and is used by the controller.

/// <summary>
/// Subscribe to listen for specific types of messages
/// </summary>
/// <typeparam name="T">The message type to listen for</typeparam>
/// <param name="self">The class setting up the event delegate callback</param>
/// <param name="eventDelegate">The event delegate to receive the messages when they arrive</param>
void Subscribe<T>(ICommunication self, MessageDelegate<T> eventDelegate) where T : Message;

/// <summary>
/// Subscribe to listen for specific types of messages
/// </summary>
/// <param name="self">The class setting up the event delegate callback</param>
/// <param name="eventDelegate">The event delegate to receive the messages when they arrive</param>
void Subscribe(ICommunication self, MessageDelegate<Message> eventDelegate);

/// <summary>
/// Remove a ICommunication object from all communication activity
/// </summary>
/// <param name="self">The ICommunication object you wish to unsubscribe</param>
void UnSubscribeAll(ICommunication self);

// Send Message
/// <summary>
/// Sends a message to any other ICommunication object listening for this message type
/// </summary>
/// <typeparam name="T">The type of message you are sending</typeparam>
/// <param name="self">The sender's ICommunication object</param>
/// <param name="value">The message to send</param>
void SendMessage<T>(ICommunication self, T value) where T : Message;

/// <summary>
/// Sends a message to a specific ICommunication object listening for this message type
/// </summary>
/// <typeparam name="T">The type of message you are sending</typeparam>
/// <param name="self">The sender's ICommunication object</param>
/// <param name="recipient">The direct recipient to send the message to</param>
/// <param name="value">The message to send</param>
void SendMessage<T>(ICommunication self, ICommunication recipient, T value) where T : Message;

/// <summary>
/// Sends a message of a particular type to any other ICommunication object that is of a particular type.
/// </summary>
/// <typeparam name="T1">The type of message to send</typeparam>
/// <typeparam name="T2">The type of object to send the message to</typeparam>
/// <param name="self">The sender's ICommunication object</param>
/// <param name="value">The message to send</param>
void SendMessage<T1, T2>(ICommunication self, T1 value) where T1 : Message where T2 : ICommunication;

Final Notes

You will want to try to manage the lifecycle of your ViewModels as you want to subscribe to messages when the ViewModel is created and UnsubscribeAll when it’s about to go away.  Because we use a weak reference it won’t prevent garbage collection but if the class is not yet garbage collected and it receives a message it may throw an exception.

You can download the source files here and they consist of 5 CS files. Note: This solution enlists the help of C# 4 dynamic. As such the implantation in CSMediator.cs will only work under the .NET 4 framework. It may be impossible to back-port to an earlier version, but if you manage it, please send me the code! It’s also great to hear from people who find it useful or have suggestions.

  • ICommunication.cs – Required to implement for all objects that wish to participate in communication
  • IMediator.cs – The interface for the mediator pattern
  • Message.cs – The various types of messages that are pre-defined. You will want to change and configure this file.
  • MessageRoute.cs – A helper class that keeps track of actions, owners, and registration information.
  • CSMediator.cs – This is the main implementation of the Mediator pattern and the IMediator interface.  Ideally, your Controller will create an instance of this class as part of it’s lifecycle and provide each ViewModel with a reference during their lifecycles.
References: