As mentioned in my last post I wanted to hit on the IMessenger services included with the CommunityToolkit.Mvvm. There is a ton of funtionality with the IMessenger but at it’s core it passes messages between an instance that sends a message and any instances that are registered to receive the message.
To do this there are two types of messegers, WeakReferenceMessenger and StrongReferenceMessenger. They are the go-between of the senders and receivers. The receiver registers with the Messenger to get either all messages or messages of a specific type. WeakReferenceMessenger doesn’t require an explicit Unregister and any instances that are registered with the WeakReferenceMessenger will go away when no longer used via standard garbage collection. StrongReferenceMessenger requires that the receiver Unregister when disposed or it won’t be garbage collected and will just sit around taking up memory being a leak.
So what is the cost of WeakReferenceMessenger versus StrongReferenceMessenger? Well, according to the documentation StrongReferenceMessenger is more memory efficient and performant. I suspect if you are writing a map application that tracks hundreds or thousands of entities on a map or maybe a real-time stock-trading app that must update in near real-time as much as possible you will want to use StrongReferernceMessenger. But there is an interesting note in the documentation:
When a recipient is not needed anymore, you should unregister it so that it will stop receiving messages
This is regardless of whether I’m using a strong ref or weak ref messenger. So why use a weak ref messenger? It’s easy when things are flying to forget to dispose and unregister. That is it’s biggest benefit. Unregistering is fairly trivial to:
Messenger.UnregisterAll(this);
That will remove the view model from receiving any more messages. But mistakes happen. I was doing a deep dive once for a colleague on an application that wasn’t running very well. It turns out he was generating tons of view models throughout the day (no big deal) and assigning an event and never unassigning it. Those view models were just hanging around in memory, not being used anywhere, taking up space. So YMMV but I suspect in most cases using the weak ref messenger is easier and safer.
Looking into the attached code, you can see in the App.xaml.cs I add an instance of a WeakReferenceMessager to the host services.
services.AddSingleton<WeakReferenceMessenger>();
services.AddSingleton<IMessenger, WeakReferenceMessenger>(provider => provider.GetRequiredService<WeakReferenceMessenger>());
This makes it convienient on the constructors of my view models that take a messenger so that it’s just passed in through DI.
Next up is figuring out what types of messages I can take. There are two ways to do this. The simplest is shown in the attached code. Create a message class with the needed parameters and mark the view model as a recipient via an interface.
WriteToChatMessage:
public record class WriteToChatMessage(string Alias, string Message, string Background, string Foreground, string FontStyle);
MainWindowViewModel class signature:
public partial class MainWindowViewModel : ObservableObject, IRecipient<WriteToChatMessage>, IDisposable
Then, in the ctor, register the view model.
public MainWindowViewModel(Dispatcher dispatcher, IMessenger messenger)
{
BindingOperations.EnableCollectionSynchronization(Chats, new());
Dispatcher = dispatcher;
Alias = "Unknown " + Guid.NewGuid().ToString();
Messenger = messenger;
Messenger.RegisterAll(this);
ChatHandler = new BasicChatHandler(Messenger);
//ChatHandler = new SystemMessageChatHandler(Messenger);
}
and finally implement an async method to Receive the message type:
public async void Receive(WriteToChatMessage message){}
So what do we do with this?
In the code there is a super basic chat handler for sending messages:
public class BasicChatHandler : ChatHandler
{
public BasicChatHandler(IMessenger messenger) : base(messenger) { }
public override Task<bool> SendChatAsync(WriteToChatMessage message)
{
Messenger.Send(message);
return Task.FromResult(true);
}
}
All it does is call Messenger.Send and sends on the message. The weak ref messenger handles passing it on to whoever needs it.
This just touched the tip of the iceberg into the functionality of the Messenger in CommunityToolkit.Mvvm. There is a base class provided that provides some of the wiring up of the code called ObservableRecipient. This provides nice functionality to Activate and Deactivate view models. The advantage is that not all view models need to always be listening. By using the IsActive property of the base class I can turn on and off which view models are listening.
When registering the view model I can provide a type of message and a lambda for handling the message instead of using the interface in the class signature. This makes is so that the class signature isn’t littered with a ton of IRecipent for each message the view model can handle.
For instance, I could change out the register in my MainWindowViewModel to:
//r is recipient, m is message
Messenger.Register<MainWindowViewModel, WriteToChatMessage>(this, (r, m) => r.Receive(m));
and not put the IRecipient<WriteToChatMessage> in the class signature. I don’t even have to use Receive as the method signature. I could use whatever method I wanted. My only concern with this is that using the IRecipient is explicit. It’s easy to see right in the class signature that this view model is a receiver and exactly what the view model is intended to handle. Otherwise I would need to dig in the code and see what types of messages are handled.
Okay, so we got here. But why? Why do all of this? Well, one of the biggest advantages I’ve seen is replacing events.
Imagine this scenario: A map application with hundreds of entities on it that are constantly moving. If a user wants to remove an entity from the map there are generally a few ways to do this:
The not recommended way:
The view model for the entity takes the parent map view model in it’s constructor and simply calls RemoveEntity on the parent. But this is problematic in that I would argue that this violates the Dependency Inversion Principle of SOLID.
A better way:
The entity view model has a “RemoveEntity” event. When the view model is constructed the parent map view model assigns a method to handle the remove that takes the entity and removes it from the observable collection. The parent then must unassign the event so we don’t get that pesky memory leak problem.
The Messenger way:
The parent map view model registers to receive RemoveEntityMessage messages. The entity takes a messenger on it’s constructor, and when the user chooses to remove the entity, it sends a RemoveEntityMessage to the Messenger. The map view model get the message and removes the entity from the observable collection.
And that’s just the parent map view model. What if I wanted to add logging every time a user adds or removes an entity? Yes I could pass in ILogger. But I could also write an EntityLogger that registers for any entity related events and logs them. Okay, what if I wanted an annoying sound to play anytime an entity is removed from the map? I could write an AnnoyingEntitySoundPlayerHandler that could listen for the entity messages and play that sound whenever an entity is added or removed from the map.
So by substituting Messenger for Event we can greatly extend the capabilities of what we can do in the application. Functionality within the application doesn’t need to know about when the view model is constructed in order to assign the event. It can just listen and respond accordingly. Pretty exciting, right?
The ultimate goal here is to show a simple way to integrate a plugin architecture. With that in mind the next post will likely extend the Chatter Chat services to use an integrated Sqlite db in the application so that we have a couple of different instances of chat handlers that can be used as plugins.
Thanks,
Brian
One thought on “Using Messenger in the CommunityToolkit.Mvvm”