This last 2 weeks, us WPF Disciples have been talking a lot about the Mediator pattern for MVVM applications. Me and Josh Smith revised my previous Mediator implementation and found some problems with it.
Problem: There was a memory leak. Once a Mediator subscribes for one or more messages it would be kept in memory since the Mediator was holding a reference to the ViewModel.
Solution: We could have just exposed an unregister method so that when you call this method the Mediator would release the instance of the ViewModel. But sometimes it is hard to know when a ViewModel is going to die… So we went a step further and created the WeakAction. The WeakAction stores the instance of the ViewModel in a WeakRefence thus the ViewModel can be garbage collected without any problem and once it does get garbage collected the Mediator automatically removes that ViewModel registration from the list.
While we were at it we PIMPED the Mediator API 🙂 We added 2 approaches to subscribe to the Mediator and we also added support for (thanks to David Anson suggestion) strongly typed parameters.
So now the Mediator offers 2 methods of registration, a declarative way of registering and an imperative way as well.
The Declarative approach. (Supported in WPF and Silverlight)
This approach uses attributes so that you can decorate the messages that act as subscribers for messages.Here is an example
1: /// <summary>
2: /// Subscribe to Message1
3: /// </summary>
4: /// <param name="message">The message to be passed</param>
5: [MediatorMessageSink(MediatorMessages.Message1, ParameterType = typeof(SomeData))]
6: public void Test(SomeData data)
7: {
8: MessageBox.Show(data.Text + "\nReceived by: ColleagueA");
9: }
So you just have to decorate your method with MediatorMessageSink attribute and the Method will get invoked every time a message is broadcasted via the Mediator.
As you can see the Mediator also supports strongly typed parameters. In order to do so you have to specify the ParameterType property on the attribute as shown in the sample code above. You can pass 0 or 1 parameters. If you don’t have any parameters just ignore the ParameterType (since this is an named Property and you can just not set it )
When using the declarative approach you must register the instance of the ViewModel to the Mediator. This is just one line of code in the ViewModel constructor. Here is how you do that
1: public ColleagueA()
2: {
3: //Register all decorated methods to the Mediator
4: Mediator.Register(this);
5: }
This will inspect all methods of the ViewModel and look for the MediatorMessageSink attribute and register the appropriate methods to the Mediator. If you are going to have a base class for the ViewModels in your project and you can put this in the constructor of your base class. As simple an that.
The Imperative approach. (Supported in WPF ONLY)
If you do not want to use the attribute based registration we offer an imperative approach where you specify a method to be registered to a specific message. This implementation is exactly as it was in the old Mediator yet now it supports strongly typed parameters.
Here is how you can use this in your code
1: public ColleagueC()
2: {
3: //Register a specific delegate to the Mediator
4: Mediator.Register(MediatorMessages.Message1,
5: (Action<SomeData>)
6: delegate(SomeData x)
7: {
8: Test = x.Text;
9: MessageBox.Show(x.Text + " \nReceived by: ColleagueC");
10: });
11: }
This method is supported in WPF only since in Silverlight you cannot call an anonymous method (via reflection). If you try to use this method in Silverlight you will get an InvalidOperationException saying “This method is not supported in Silverlight”.
Conclusion
I packaged the Mediator in a class library so that you can go ahead and add a reference and reuse this library in your day to day projects.As always feedback is much appreciated.