MVVM Mediator Pattern

About 1 year ago a good friend of mine Marlon Grech wrote a lovely article on MVC + M. You can read all about Marlons great article over at http://marlongrech.wordpress.com/2008/03/20/more-than-just-mvc-for-wpf/

Essentially what Marlon did was create a message system to allow disparate MVC classes to communicate with each other. The original code by Marlon was spot on, but I just didn’t like the way he was using strings for his messages, but other than that there is very little change from my code here to his code. So well done Marlon, you are ace.

Anyway the idea behind Marlons original post was using the MVC pattern along with an additional class called the Mediator whom knew about messages and who to notify when something happened that warrants a message being sent out.

These days most folk will use the MVVM pattern when developing WPF. I have a small demo app that does the following.

  1. It has 2 textboxes, one of which is a writable textbox, which is used to send messages via the Mediator
  2. The 2nd textbox is refreshed via the message that is sent via the Mediator when the 1st textbox changes

Lets have a look at some code:

1st a helper class (lifted from Marlons blog)

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  
   6:  namespace MediatorDemo
   7:  {
   8:      /// <summary>
   9:      /// The multi dictionary is a dictionary that contains 
  10:      /// more than one value per key
  11:      /// </summary>
  12:      /// <typeparam name="T">The type of the key</typeparam>
  13:      /// <typeparam name="K">The type of the list contents</typeparam>
  14:      public class MultiDictionary<T, K>
  15:          : Dictionary<T, List<K>>
  16:      {
  17:  
  18:          #region Private Methods
  19:          //checks if the key is already present
  20:          private void EnsureKey(T key)
  21:          {
  22:              if (!ContainsKey(key))
  23:              {
  24:                  this[key] = new List<K>(1);
  25:              }
  26:              else
  27:              {
  28:                  if (this[key] == null)
  29:                      this[key] = new List<K>(1);
  30:              }
  31:          }
  32:          #endregion
  33:  
  34:          #region Public Methods
  35:          /// <summary>
  36:          /// Adds a new value in the Values collection
  37:          /// </summary>
  38:          /// <param name="key">The key where to place the 
  39:          /// item in the value list</param>
  40:          /// <param name="newItem">The new item to add</param>
  41:          public void AddValue(T key, K newItem)
  42:          {
  43:              EnsureKey(key);
  44:              this[key].Add(newItem);
  45:          }
  46:  
  47:  
  48:          /// <summary>
  49:          /// Adds a list of values to append to the value collection
  50:          /// </summary>
  51:          /// <param name="key">The key where to place the item in the value list</param>
  52:          /// <param name="newItems">The new items to add</param>
  53:          public void AddValues(T key, IEnumerable<K> newItems)
  54:          {
  55:              EnsureKey(key);
  56:              this[key].AddRange(newItems);
  57:          }
  58:  
  59:          /// <summary>
  60:          /// Removes a specific element from the dict
  61:          /// If the value list is empty the key is removed from the dict
  62:          /// </summary>
  63:          /// <param name="key">The key from where to remove the value</param>
  64:          /// <param name="value">The value to remove</param>
  65:          /// <returns>Returns false if the key was not found</returns>
  66:          public bool RemoveValue(T key, K value)
  67:          {
  68:              if (!ContainsKey(key))
  69:                  return false;
  70:  
  71:              this[key].Remove(value);
  72:  
  73:              if (this[key].Count == 0)
  74:                  this.Remove(key);
  75:  
  76:              return true;
  77:          }
  78:  
  79:          /// <summary>
  80:          /// Removes all items that match the prediacte
  81:          /// If the value list is empty the key is removed from the dict
  82:          /// </summary>
  83:          /// <param name="key">The key from where to remove the value</param>
  84:          /// <param name="match">The predicate to match the items</param>
  85:          /// <returns>Returns false if the key was not found</returns>
  86:          public bool RemoveAllValue(T key, Predicate<K> match)
  87:          {
  88:              if (!ContainsKey(key))
  89:                  return false;
  90:  
  91:              this[key].RemoveAll(match);
  92:  
  93:              if (this[key].Count == 0)
  94:                  this.Remove(key);
  95:  
  96:              return true;
  97:          }
  98:          #endregion
  99:      }
 100:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

This simply allows more than 1 object to be registered for a particular message

Next comes the Mediator which is a singleton, and know how to send and register messages against callback, which is what I have changed. In Marlons original code he used Strings, where as I am now using Action<Object> delegates as callbacks. A minor improvement I feel.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  
   6:  namespace MediatorDemo
   7:  {
   8:      /// <summary>
   9:      /// Available cross ViewModel messages
  10:      /// </summary>
  11:      public enum ViewModelMessages { UserWroteSomething = 1 };
  12:  
  13:  
  14:      public sealed class Mediator
  15:      {
  16:          #region Data
  17:          static readonly Mediator instance = new Mediator();
  18:          private volatile object locker = new object();
  19:  
  20:          MultiDictionary<ViewModelMessages, Action<Object>> internalList
  21:              = new MultiDictionary<ViewModelMessages, Action<Object>>();
  22:          #endregion
  23:  
  24:          #region Ctor
  25:          //CTORs
  26:          static Mediator()
  27:          {
  28:  
  29:  
  30:          }
  31:  
  32:          private Mediator()
  33:          {
  34:  
  35:          }
  36:          #endregion
  37:  
  38:          #region Public Properties
  39:  
  40:          /// <summary>
  41:          /// The singleton instance
  42:          /// </summary>
  43:          public static Mediator Instance
  44:          {
  45:              get
  46:              {
  47:                  return instance;
  48:              }
  49:          }
  50:  
  51:          #endregion
  52:  
  53:          #region Public Methods
  54:          /// <summary>
  55:          /// Registers a Colleague to a specific message
  56:          /// </summary>
  57:          /// <param name="callback">The callback to use 
  58:          /// when the message it seen</param>
  59:          /// <param name="message">The message to 
  60:          /// register to</param>
  61:          public void Register(Action<Object> callback,
  62:              ViewModelMessages message)
  63:          {
  64:              internalList.AddValue(message, callback);
  65:          }
  66:  
  67:  
  68:          /// <summary>
  69:          /// Notify all colleagues that are registed to the 
  70:          /// specific message
  71:          /// </summary>
  72:          /// <param name="message">The message for the notify by</param>
  73:          /// <param name="args">The arguments for the message</param>
  74:          public void NotifyColleagues(ViewModelMessages message,
  75:              object args)
  76:          {
  77:              if (internalList.ContainsKey(message))
  78:              {
  79:                  //forward the message to all listeners
  80:                  foreach (Action<object> callback in
  81:                      internalList[message])
  82:                          callback(args);
  83:              }
  84:          }
  85:          #endregion
  86:  
  87:      }
  88:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

So how does this work, well lets see a ViewModel that sends a message via the mediator, notice the NotifyColleagues(..) method usage below.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel;
   4:  using System.Linq;
   5:  using System.Text;
   6:  using System.Windows.Input;
   7:  using System.Windows.Data;
   8:  
   9:  
  10:  namespace MediatorDemo
  11:  {
  12:      public class WritersViewModel : ViewModelBase
  13:      {
  14:          private String writerText = null;
  15:  
  16:  
  17:          public WritersViewModel()
  18:          {
  19:  
  20:  
  21:          }
  22:  
  23:          public String WriterText
  24:          {
  25:              get { return writerText; }
  26:              set
  27:              {
  28:                  writerText = value;
  29:                  NotifyChanged("WriterText");
  30:                  //alert others about this change
  31:                  //via Mediator
  32:                  Mediator.Instance.NotifyColleagues(
  33:                      ViewModelMessages.UserWroteSomething,
  34:                          writerText);
  35:              }
  36:          }
  37:  
  38:      }
  39:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

And how about dealing with a message from the Mediator, this is fairly simple, though we do have to cast the results from Object to the Type we are expecting. This is necessary as the Mediator uses Action<Object> delegates as callbacks, where Object could be any Type of course.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.ComponentModel;
   4:  using System.Linq;
   5:  using System.Text;
   6:  using System.Windows.Input;
   7:  
   8:  
   9:  namespace MediatorDemo
  10:  {
  11:      public class ReadersViewModel : ViewModelBase
  12:      {
  13:          private String readText = String.Empty;
  14:  
  15:  
  16:          public ReadersViewModel()
  17:          {
  18:              //register to the mediator for the 
  19:              //UserWroteSomething message
  20:              Mediator.Instance.Register(
  21:  
  22:                  //Callback delegate, when message is seen
  23:                  (Object o) =>
  24:                  {
  25:                      ReadText = (String)o;
  26:                  }, ViewModelMessages.UserWroteSomething);
  27:          }
  28:  
  29:  
  30:          public String ReadText
  31:          {
  32:              get { return readText; }
  33:              set
  34:              {
  35:                  readText = value;
  36:                  NotifyChanged("ReadText");
  37:              }
  38:          }
  39:      }
  40:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

And that is really all there is to it. You now have a Writer ViewModel that will notify a totally disconnected Reader ViewModel about a change, via the messaging providing by the Mediator/Action<Object> delegate callbacks, that were registered for the message by the class that wants to do something based on the message being registered with the Mediator.

As I say this is all thanks to Marlon, well done Marlon.

And here is a small demo app for you to play with

mediatordemo.zip

About these ads

23 thoughts on “MVVM Mediator Pattern

  1. Raul Mainardi Neto says:

    o.O nice stuff… seen the MVC post before, nice use of it.. you study it a little deeper… Thank for sharing Sacha

    All best mate

    Raul

  2. Ted says:

    Cool, I like it! Now you should just implement so that the mediator could be configured on what thread to dispatch each message, individual listeners might want the message on different threads… maybe auto-hookup on the dispatcher that was used when registering to listen for a message, or as a parameter maybe… Just an idea… :)

    Cheers!

  3. Paul says:

    Have you taken a look at http://www.codeplex.com/CompositeWpF?

    They have a very nice implementation of Pub Sub Messaging within WPF (their Event Aggregator). Why re-invent something that is already there and works very very well?

    I am not trying to downplay what you have done, because it is always good to see alternate implementations, just wasnt sure if you had seen their work. If you are heading down a messaging path, the work they have done has a lot of stuff that you will eventually try to implement.

  4. sacha says:

    Paul

    Thanks for that, and yes yes I have heard and worked with PRISM aka Composit WPF, which in my mind is code bloat, and should not be used unless you have a very large project. It reminds me of the Smart Client Software Factory which we are in the process of ripping out.

    I just dont like heavy frameworks, and PRISM I am afraid to say is one such framework.

  5. The reason why the mediator pattern usually uses a string and not an enum is that every time you want to add a new message type, you have to modify the enum. With a string it is far more flexible.

  6. Paul says:

    Sorry to hear your opinion of Prism. I agree that CAB was huge, but Prism has taken a much different approach.

    Even if you dont want to use the framework, it would be worth your while IMO, to take a look at the source for the event aggregator. They handle the case you were talking about above ( UI Thread, etc. ). It really has some neat concepts in it.

    Most of the work I do ends up needing the things that Prism provides. So I look at something like Prism from a completely different viewpoint.

  7. sacha says:

    I hear you, I think it is ok to take stuff from PRISM, but not use the whole lot together, its way to much IMHO.

    But agreed there are “some good parts” to it.

  8. sacha says:

    Mladen

    Fair ewnough, I still would prefer to use enums as I will know what messages I want to respond to in advance and I do not want to add any more dynamically at runtime, so its is fine with enums for my purposes

  9. Nice article but you should change the property names of the MultiDictionary to reflect the IDictionary property names (Add, AddRange, Remove, RemoveAll instead of AddValue, AddValues, RemoveValue, RemoveAllValues).

    Also change “EnsureKey” to “EnsureKeyExists” and “NotifyColleagues” to “NotifySubscribers”

  10. Jose says:

    The question I have with this is that to have an enumeration in the mediator class means that it can’t really be reused. You will have to add the Mediator code files into every project you use and populate the enumeration. Not a big deal, it’s just copy and paste. But can you think of a way to make the mediator generic so the application chooses the enumeration values?

  11. sacha says:

    If you can wait a while I am working on rather cool MVVM framework with much better Mediator in it also.

  12. nitzzzu says:

    Hi Sacha. The mediator pattern is great. But you can only broadcast messages. Let’s say I have 3 modules in my super cool LOB application: Module1, Module2, Module3. Module1 has a list of customers (some of them are selected) and it’s bound to Module1ViewModel. Module2 and Module3 both subscribe to ReceivedSelectedCustomers message (this message will be published by Module1). At a certain time we want to get the selected customers from Module2 only and we sendmessage(Module1.GetSelectedCustomers). Module1 receives the message and wants to send back a reply (ReceivedSelectedCustomers). The problem is that we don’t have the sender of Module1.GetSelectedCustomers because the message was broadcasted. Is there an elegant way to solve this? thanks

  13. sacha says:

    nitzzzu

    What you say is true, it is a broadcast mechanism, perhaps PRISMs event Aggregator would suit your needs better

  14. Saragani says:

    Hi, what about unregistering?
    Lets assume that some of the viewmodels can die (for example some of the view models are viewmodels for an item inside a list), or maybe your application has 1 mediator and it loaded a form with several viewmodels that talk to each other).

    If a viewmodel registers then when the code exits the scope the viewmodel was created in then it doesn’t die!

    It can create memory leaks. Any solution?

    • sacha says:

      Yeah do not use this post, use the Mediator found in my MVVM framework Cinch, which has full documentation and weak references. Check it out at cinch.codeplex.com, and read the articles there

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