Inter-View Model Communication Using MVVM Light Messenger
This is the second post in a series that will attempt to illustrate a way to decouple your application from the traditional code behind paradigm.
Sample code can be found here:
https://bitbucket.org/jpmedina/mvvmcalculator
Note, the application implements a contrived calculator as such there is validation added that would, or might, be unnecessary using the correct input scope for the text boxes.
This code is free to use in any shape and form. However, you cannot claim that you wrote the original although you can change it in any manor you see fit including using code in published applications to the Windows Phone Store. I would appreciate any attribution.
This post uses the code in the sample project in the
{View}xaml files where
{View} is a variation on the Calculator. The
MainPage.xaml contains buttons that will load each of these pages, the behavior is identical.
Traditional View Model to View Model Communication
In the
last post we discussed how traditional application logic is in the code behind for a given view. We ended up learning that not only is this not always necessary but we can gain a bit of flexibility by relocating this logic to the View Model while using RelayCommand and RelayCommand<T> to glue the UI to this logic. In this post we'll take a similar approach, first we'll discuss the traditional communication mechanim between different View Models in a system, this is possibly the same mechanism for other parts of the system, and a different approach that will give us a more flexibly approach for inter-view model communications.
A word of caution, pick the approach that best suits the application at hand. Although this can be a great approach for many applications there are some drawbacks, which will be discussed later. That being said, you can view the view model code in the MvvmCalculator.ViewModels project, specifically CalculatorViewModelTraditional.cs, CalculatorViewModelRefactored.cs and CalculatorViewModelAutomatic.cs. Also if you were following the last post you'll notice some refactoring that has happened, the logic from CalculatorViewModel.cs (although this class has remained unchanged) has been extracted to a base class to make creating additional view models easier.
As an extension to the first post we're going to add persistence to our calculator application, although this is a bit contrived persistence is a common application attribute. Let's assume a user wants to be able to backup their calculation results and actually rerun them. It would be sad if you had to input all of your numerical analysis each time you reopened the application. In order to do this we create a settings dialog that will open up when the settings icon is pressed on the application bar or menu item. I chose to do this inline as using page navigation can add a bit of complexity. The application bar will fire a command to our settings view model which will trigger a visibility value causing the dialog to open. You can find the code for the settings view model in SettingsViewModel.cs, SettingsViewModelRefactored.cs and SettingsViewModelAutomatics.cs.
In order for us to save the calculations, I've chosen isolated storage for simplicity, the settings view model needs the calculations. The traditional way to do this is to either create the dependency within the settings view model or what I've chosen here, inject an instance of the calculator view model into the settings view model. Ideally we would use an interface in order to allow for testing, but each view model contains few dependency and testing isn't our goal in this series. Here's the constructor:
public SettingsViewModel(CalculatorViewModelTraditional calculatorViewModel)
{
_calculatorViewModel = calculatorViewModel;
this.ShowDialogVisibility = Visibility.Collapsed;
if(!Settings.Contains("Settings"))
{
InternalSaveSettingsHandler();
}
else
{
InternalRestoreSettingsHandler();
}
}
This constructor sets the visibility of the settings dialog to "Collapsed" so that it doesn't show unless the user requests it. If there are no settings saved it creates the default settings otherwise it will reload them. The dialog simply contains a toggle button and a save/reload button (the toggle button doesn't do anything). Note how this is a coupling between the SettingsViewModel and the CalculatorViewModel. Any changes that are made to the latter require updates in the former. Although this is a small application as you can imagine if we expand it to include more functionality this might become a maintenance nightmare. However, this is very common. Even if we abstracted the CalculatorViewModel with some other placeholder, for example a model, we would still be tied to the functionality. If this is common and the more traditional approach, as it is in ASP.NET MVC, WebAPI or WCF, what approach can we take to reduce this coupling?
Inter-View Model Communication Via Messenger
Of course this is a leading question as we already have an approach. You wouldn't be reading part 2 if you didn't have an idea of what it was. That's right we can use MVVM Light's
Messenger, or rather messaging, mechanism. However, we can't just throw that into the mix, we need to come up with a model, that will be a good representation of what's going to be saved in isolated storage. You can find the
SavedSettingsModel in
MvvmCalculator.Core project under
Models. That's the first part, secondly we need a message to represent saving a model, a la
MOA (Message Oriented Architecture). Messages should represent actions taken on the data in the message, for example if you want to save settings, a saved settings message should be sent. You can find these messages, also in the
MvvmCalculator.Core project but this time under
Messages. I've created a
MessageBase class that contains common elements that are used for messages. Now that we have our model to save and the messages that represent actions on the model what comes next? As is common once you have your building blocks you need glue to hold them together. That glue is the MVVM
Messenger class.
Take a look at
SettingsViewModelBase.cs, particularly the constructor, this contains the common code across all SettingsViewModels. Notice this method being called:
private void CreateRegistrations()
{
Messenger.Default.Register<SettingsRestoredMessage>(this, this.SettingsRestoredHandler);
Messenger.Default.Register<SettingsSavedMessage>(this, this.SettingsSavedHandler);
}
Here we're using the default messenger, you can define others but the default make things simpler. Each line is a registration that tells the messenger, which is essentially an in memory service bus, that for each of the message types call the method defined on "this", and pass the message to it. You can also use a statement lambda if you don't want to define a separate function:
Messenger.Default.Register<SettingsRestoredMessage>(
this,
m =>
{
//Handle message here,
}
);
Both ways can produce the same result although if you have a lot of logic, the method makes it easier to work with. Great now the
SettingsViewModel doesn't need to care about the
CalculatorViewModel, in fact you can look at
SettingsViewModelRefactored and the
SettingsViewModelBase to confirm that is the case. However, that's only half of the story, we need to be able to tell other subscribers, those subscribing to
SaveSettingsMessage, that there are settings to save. How is that done? Here's how (in
SettingsViewModelBase):
protected virtual void SaveSettingsHandler()
{
Messenger.Default.Send(
new SaveSettingsMessage(
new SavedSettingsModel
{
VibrateOn = this.VibrateOn
}));
}
Here the
SettingsViewModel tells the messenger to notify observers about a
SaveSettingsMessage (notice here we use the
SavedSettingsModel to transmit data). The
Messenger class with call each method for each observer that is registered to be notified about
SavedSettingsMessage's. Note, the virtual behavior is used for
SettingsViewModelAutomatic. If you look at the corresponding
CalculatorViewModelRefactored you'll notice that it too create a registration for
SaveSettingsMessage. That's because it's the only one that should know about calculations and if it wants them to be saved it needs to add them to the message and pass it along. In this case we have to send a different message,
SavingSettingsMessage, in order to avoid an infinite loop.
One last piece that needs to be mentioned. The behavior for the saving is actually in the
SettingsModel. This will be familiar to those using
MVC (
Model-View-Controller), the Model is the one that knows how to interact with the data, saving restoring, etc. This is another way we can decouple our persistence mechanism from other parts of the system. If we decide that
IsolatedStorage needs to be swapped out or supplemented, via SkyDrive, GoogleDrive,
SQLCE or other, than we only have one place to change it and the
SettingsViewModel and
CalculatorViewModel benefit without any code changes (unless of course you change the message by removing or renaming properties). The
SettingsModel sends out additional messages when the settings are saved and when they are being restored. This is truly cross view model and even model communication without each component having to be directly aware of each other. The last view model pair,
SettingsViewModelAutomatic and
CalculatorViewModelAutomatic, illustrate that rather than wait on the user to ask for persistence you can perform it automatically. This way you can support Tomb-Stoning without having to use event handlers in the UI.
I won't go over all of the code as I'm hoping it's clear. However, feel free to send me an email or add a comment to the blog. However, I need to go over some caveats to using this approach.
Messenger This Messenger That, And When It's Not
For a large number of scenarios you'll have no issues with using messages via
Messenger, but there will be times when you might find that a handler doesn't get called, or sometimes it gets called to often. Here's a list of issues and possible reason:
Handlers are not being called but are being registered
You'll run into to this if the class that has the handler registration is not in memory before the Messenger goes through the list of observers. This is because of a temporal message workflow. It's advisable that you document you workflows, something that will be discussed in a continuing blog post, so that you can keep track of what message needs to happen when. Also if necessary and you're using an IOC container you can make a call to
GetAllInstances,
GetInstance or a relevant
Get/
Resolve method in order to guarantee that the component is initialized.
Handlers are being called multiple times
As its advisable that you document your message workflows so you can determine if this is the correct behavior. If you register the same message in the same class with the same registration, it will be register again and hence called that many times. You can either set your instance to be a singleton, if using an IOC container, or create a private static bool flag value and check before registering more than once. Consider using the
Unregister method of
Messenger if you no longer want or need to receive events.
Messages That Interact With the Binding Are Slow
Are far as I can tell the Messenger is synchronous so if you have long running message interactions your best best is to find a way to make the asynchronous, this will depend on the platform, Windows Phone 7 vs 8 and Windows 8 Store apps.
To Message Or Not To Message That Is the Question
To wrap up, you can gain a loosely coupled application by forgoing the traditional view model coupling or IOC injection pattern and instead using the MOA pattern via MVVM Light's
Messenger class. This approach is simple and effective for most approaches but you must be careful and evaluate the needs of your application. You must be aware that using messaging can introduce temporal workflows where one view model must be "alive" or in memory before it will receive or send messages. Also you have to be careful to only register handlers only as often as needed. Given a few of the issues with messaging it is my opinion that for most cases the benefits (decoupling, flexibility, testing, maintenance, etc) greatly outweigh any negatives.
In the next post I hope to show a more advanced messaging workflow by building on the code we currently have. Until then feel free to comment as well as pull the code down and run it.