The Microsoft.Toolkit.Mvvm
package is a modern, lightweight, efficient and self-contained set of MVVM primitives that can be used to get started building an app with the MVVM pattern. It's meant to offer great interoperability with other BCL types and other 1st party libraries (such as Microsoft.Extensions.DependencyInjection
), and to provide an easy to use and minimal set of tools to hit the ground running when setting up a new project.
It's structured as follows:
Microsoft.Toolkit.Mvvm.ComponentModel
: contains base classes for observable objects, implementing theINotifyPropertyChanging
andINotifyPropertyChanged
interfaces.Microsoft.Toolkit.Mvvm.DependencyInjection
: contains helper types to easily setup dependency injection into an application, by leveraging theMicrosoft.Extensions.DependencyInjection
package.Microsoft.Toolkit.Mvvm.Input
: containsICommand
implementations with support for strong typing and async operations.Microsoft.Toolkit.Mvvm.Messaging
: contains types offering support for a fast and efficient publisher-subscriber infrastructure.
The package is inspired by the popular MvvmLight
package, and developers trying to migrate from that particular library should feel right at home with this new toolkit package.
The ObservableObject
is a base class for objects that are observable by implementing the INotifyPropertyChanged
and INotifyPropertyChanging
interfaces. It can be used as a starting point for all kinds of objects that need to support property change notifications.
ObservableObject
has the following main features:
- It provides a base implementation for
INotifyPropertyChanged
andINotifyPropertyChanging
, exposing thePropertyChanged
andPropertyChanging
events. - It provides a series of
SetProperty
methods that can be used to easily set property values from types inheriting fromObservableObject
, and to automatically raise the appropriate events. - It provides the
SetPropertyAndNotifyOnCompletion
method, which is analogous toSetProperty
but with the ability to setTask
properties and raise the notification events automatically when the assigned tasks are completed. - It exposes the
OnPropertyChanged
andOnPropertyChanging
methods, which can be overridden in derived types to customize how the notification events are raised.
Here's an example of how to implement notification support to a custom property:
public class User : ObservableObject
{
private string name;
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
}
The provided SetProperty<T>(ref T, T, string)
method checks the current value of the property, and updates it if different, and then also raises the relevant events automatically. The property name is automatically captured through the use of the [CallerMemberName]
attribute, so there's no need to manually specify which property is being updated.
A common scenario, for instance, when working with database items, is to create a wrapping "bindable" model that relays properties of the database model, and raises the property changed notifications when needed. This is also needed when wanting to inject notification support to models, that don't implement the INotifyPropertyChanged
interface. ObservableObject
provides a dedicated method to make this process simpler. For the following example, User
is a model directly mapping a database table, without inheriting from ObservableObject
:
public class ObservableUser : ObservableObject
{
private readonly User user;
public ObservableUser(User user) => this.user = user;
public string Name
{
get => user.Name;
set => Set(() => user.Name, value);
}
}
The SetProperty<T>(Expression<Func<T>>, T, string)
method makes creating these wrapping properties extremely simple, as it takes care of both retrieving and setting the target properties while providing an extremely compact API.
If a property is a Task
it's necessary to also raise the notification event once the task completes, so that bindings are updated at the right time. eg. to display a loading indicator or other status info on the operation represented by the task. ObservableObject
has an API for this scenario:
public class MyModel : ObservableObject
{
private Task<int> requestTask;
public Task<int> RequestTask
{
get => requestTask;
set => SetPropertyAndNotifyOnCompletion(ref requestTask, () => requestTask, value);
}
public void RequestValue()
{
RequestTask = WebService.LoadMyValueAsync();
}
}
Here the SetPropertyAndNotifyOnCompletion<TTask>(ref TTask, Expression<Func<TTask>>, TTask, string)
method will take care of updating the target field, monitoring the new task, if present, and raising the notification event when that task completes. This way, it's possible to just bind to a task property and to be notified when its status changes.
The RelayCommand
and RelayCommand<T>
are ICommand
implementations that can expose a method or delegate to the view. These types act as a way to bind commands between the viewmodel and UI elements.
RelayCommand
and RelayCommand<T>
have the following main features:
- They provide a base implementation of the
ICommand
interface. - They also implement the
IRelayCommand
(andIRelayCommand<T>
) interface, which exposes aNotifyCanExecuteChanged
method to raise theCanExecuteChanged
event. - They expose constructors taking delegates like
Action
andFunc<T>
, which allow the wrapping of standard methods and lambda expressions.
The following shows how to set up a simple command:
public class MyViewModel : ObservableObject
{
public MyViewModel()
{
IncrementCounterCommand = new RelayCommand(IncrementCounter);
}
private int counter;
public int Counter
{
get => counter;
private set => Set(ref counter, value);
}
public ICommand IncrementCounterCommand { get; }
private void IncrementCounter() => Counter++;
}
And the relative UI could then be (using WinUI XAML):
<Page
x:Class="MyApp.Views.MyPage"
xmlns:viewModels="using:MyApp.ViewModels">
<Page.DataContext>
<viewModels:MyViewModel x:Name="ViewModel"/>
</Page.DataContext>
<StackPanel Spacing="8">
<TextBlock Text="{x:Bind ViewModel.Counter, Mode=OneWay}"/>
<Button
Content="Click me!"
Command="{x:Bind ViewModel.IncrementCounterCommand}"/>
</StackPanel>
</Page>
The Button
binds to the ICommand
in the viewmodel, which wraps the private IncrementCounter
method. The TextBlock
displays the value of the Counter
property and is updated every time the property value changes.
The AsyncRelayCommand
and AsyncRelayCommand<T>
are ICommand
implementations that extend the functionalities offered by RelayCommand
, with support for asynchronous operations.
AsyncRelayCommand
and AsyncRelayCommand<T>
have the following main features:
- They extend the functionalities of the non-asynchronous commands included in the library, with support for
Task
-returning delegates. - They expose an
ExecutionTask
property that can be used to monitor the progress of a pending operation, and anIsRunning
that can be used to check when an operation completes. This is particularly useful to bind a command to UI elements such as loading indicators. - They implement the
IAsyncRelayCommand
andIAsyncRelayCommand<T>
interfaces, which means that viewmodel can easily expose commands using these to reduce the tight coupling between types. For instance, this makes it easier to replace a command with a custom implementation exposing the same public API surface, if needed.
Let's imagine a scenario similar to the one described in the RelayCommand
sample, but a command executing an asynchronous operation:
public class MyViewModel : ObservableObject
{
public MyViewModel()
{
DownloadTextCommand = new AsyncRelayCommand(DownloadText);
}
public IAsyncRelayCommand DownloadTextCommand { get; }
private Task<string> DownloadText()
{
return WebService.LoadMyTextAsync();
}
}
With the related UI code:
<Page
x:Class="MyApp.Views.MyPage"
xmlns:viewModels="using:MyApp.ViewModels">
<Page.DataContext>
<viewModels:MyViewModel x:Name="ViewModel"/>
</Page.DataContext>
<StackPanel Spacing="8">
<TextBlock>
<Run Text="Task status:"/>
<Run Text="{x:Bind ViewModel.DownloadTextCommand.ExecutionTask.Status, Mode=OneWay}"/>
<LineBreak/>
<Run Text="Result:"/>
<Run
xmlns:ex="using:Microsoft.Toolkit.Extensions"
Text="{x:Bind ex:TaskExtensions.GetResultOrDefault(ViewModel.DownloadTextCommand.ExecutionTask), Mode=OneWay}"/>
</TextBlock>
<Button
Content="Click me!"
Command="{x:Bind ViewModel.DownloadTextCommand}"/>
<ProgressRing IsActive="{x:Bind ViewModel.DownloadTextCommand.IsRunning, Mode=OneWay}"/>
</StackPanel>
</Page>
Upon clicking the Button
, the command is invoked, and the ExecutionTask
updated. When the operation completes, the property raises a notification which is reflected in the UI. In this case, both the task status and the current result of the task are displayed. Note that to show the result of the task, it is necessary to use the TaskExtensions.GetResultOrDefault
method - this provides access to the result of a task that has not yet completed without blocking the thread (and possibly causing a deadlock).
Ioc (Inversion of control)
The Ioc
class is a type that facilitates the use of the IServiceProvider
type. It's powered by the Microsoft.Extensions.DependencyInjection
package, which provides a fully featured and powerful DI set of APIs, and acts as an easy to setup and use IServiceProvider
.
The main entry point is the ConfigureServices
method, which can be used like so:
// Register the services at startup
Ioc.Default.ConfigureServices(services =>
{
services.AddSingleton<IFilesService, FilesService>();
services.AddSingleton<ISettingsService, SettingsService>();
// Other services here...
});
// Retrieve a service instance when needed
IFilesService fileService = Ioc.Default.GetService<IFilesService>();
The Ioc.Default
property offers a thread-safe IServiceProvider
instance that can be used anywhere in the application to resolve services. The ConfigureService
method handles the initialization of that service. It is also possible to create different Ioc
instances and to initialize each with different services.
One powerful feature that is available is "constructor injection", which means that the DI service provider is able to automatically resolve indirect dependencies between registered services when creating instances of the type being requested. Consider the following service:
public class ConsoleLogger : ILogger
{
private readonly IFileService FileService;
private readonly IConsoleService ConsoleService;
public FileLogger(
IFileService fileService,
IConsoleService consoleService)
{
FileService = fileService;
ConsoleService = consoleService;
}
// Methods for the IFileLogger interface here...
}
Here we have a ConsoleLogger
implementing the ILogger
interface, and requiring IFileService
and IConsoleService
instances. Constructor injection means the DI service provider will "automagically" gather all the necessary services, like so:
// Register the services at startup
Ioc.Default.ConfigureServices(services =>
{
services.AddSingleton<IFileService, FileService>();
services.AddSingleton<IConsoleService, ConsoleService>();
services.AddSingleton<ILogger, ConsoleLogger>();
});
// Retrieve a console logger with constructor injection
ConsoleLogger consoleLogger = Ioc.Default.GetService<ConsoleLogger>();
The DI service provider will automatically check whether all the necessary services are registered, then it will retrieve them and invoke the constructor for the registered ILogger
concrete type, to get the instance to return - all done automatically!
The Messenger
class (with the accompanying IMessenger
interface) can be used to exchange messages between different objects. This can be useful to decouple different modules of an application without having to keep strong references to types being referenced. It is also possible to send messages to specific channels, uniquely identified by a token, and to have different messengers in different sections of an application.
The Messenger
type is responsible for maintaining links between recipients (receivers of messages) and their registered message types, with relative message handlers. Any object can be registered as a recipient for a given message type using a message handler, which will be invoked whenever the Messenger
instance is used to send a message of that type. It is also possible to send messages through specific communication channels (each identified by a unique token), so that multiple modules can exchange messages of the same type without causing conflicts. Messages sent without a token use the default shared channel.
There are two ways to perform message registration: either through the IRecipient<TMessage>
interface, or using an Action<TMessage>
delegate acting as message handler. The first lets you register all the handlers with a single call to the RegisterAll
extension, which automatically registers the recipients of all the declared message handlers, while the latter is useful when you need more flexibility or when you want to use a simple lambda expression as a message handler.
Similar to the Ioc
class, Messenger
exposes a Default
property that offers a thread-safe implementation built-in into the package. It is also possible to create multiple Messenger
instances if needed, for instance if a different one is injected with a DI service provider into a different module of the app (for instance, multiple windows running in the same process).
Consider the following:
// Create a message
public class LoggedInUserChangedMessage : ValueChangedMessage<User>
{
public LoggedInUserChangedMessage(User user) : base(user)
{
}
}
// Register a message in some module
Messenger.Default.Register<LoggedInUserChangedMessage>(this, m =>
{
// Handle the message here
});
// Send a message from some other module
Messenger.Default.Send(new LoggedInUserChangedMessage(user));
Let's imagine this message type being used in a simple messaging application, which displays a header with the user name and profile image of the currently logged user, a panel with a list of conversations, and another panel with messages from the current conversation, if one is selected. Let's say these three sections are supported by the HeaderViewModel
, ConversationsListViewModel
and ConversationViewModel
types respectively. In this scenario, the LoggedInUserChangedMessage
message might be sent by the HeaderViewModel
after a login operation has completed, and both those other viewmodels might register handlers for it. For instance, ConversationsListViewModel
will load the list of conversations for the new user, and ConversationViewModel
will just close the current conversation, if one is present.
The Messenger
class takes care of delivering messages to all the registered recipients. Note that a recipient can subscribe to messages of a specific type. Note that inherited message types are not registered in the default Messenger
implementation.
When a recipient is not needed anymore, you should unregister it so that it will stop receiving messages. You can unregister either by message type, by registration token, or by recipient:
// Unregisters the recipient from a message type
Messenger.Default.Unregister<LoggedInUserChangedMessage>(this);
// Unregisters the recipient from a message type in a specified channel
Messenger.Default.Unregister<LoggedInUserChangedMessage, int>(this, 42);
// Unregister the recipient from all messages, across all channels
Messenger.Default.UnregisterAll(this);
Warning
The Messenger
implementation uses strong references to track the registered recipients. This is done for performance reasons, and it means that each registered recipient should manually be unregistered to avoid memory leaks. That is, as long as a recipient is registered, the Messenger
instance in use will keep an active reference to it, which will prevent the garbage collector from being able to collect that instance. You can either handle this manually, or you can inherit from ObservableRecipient
, which by default automatically takes care of removing all the message registrations for recipient when it is deactivated (see docs on ObservableRecipient
for more info about this).
Another useful feature of messenger instances is that they can also be used to request values from a module to another. In order to do so, the package includes a base RequestMessage<T>
class, which can be used like so:
// Create a message
public class LoggedInUserRequestMessage : RequestMessage<User>
{
}
// Register the receiver in a module
Messenger.Default.Register<LoggedInUserRequestMessage>(this, m =>
{
m.Reply(CurrentUser); // Assume this is a private member
});
// Request the value from another module
User user = Messenger.Default.Send<LoggedInUserRequestMessage>();
The RequestMessage<T>
class includes an implicit converter that makes the conversion from a LoggedInUserRequestMessage
to its contained User
object possible. This will also check that a response has been received for the message, and throw an exception if that's not the case. It is also possible to send request messages without this mandatory response guarantee: just store the returned message in a local variable, and then manually check whether a response value is available or not. Doing so will not trigger the automatic exception if a response is not received when the Send
method returns.
The same namespace also includes base requests message for other scenarios: AsyncRequestMessage<T>
, CollectionRequestMessage<T>
and AsyncCollectionRequestMessage<T>
.
Here's how you can use an async request message:
// Create a message
public class LoggedInUserRequestMessage : AsyncRequestMessage<User>
{
}
// Register the receiver in a module
Messenger.Default.Register<LoggedInUserRequestMessage>(this, m =>
{
m.Reply(GetCurrentUserAsync()); // We're replying with a Task<User>
});
// Request the value from another module (we can directly await on the request)
User user = await Messenger.Default.Send<LoggedInUserRequestMessage>();
The ObservableRecipient
type is a base class for observable objects that also acts as recipients for messages. This class is an extension of ObservableObject
which also provides built-in support to use the IMessenger
type.
The ObservableRecipient
type is meant to be used as a base for viewmodels that also use the IMessenger
features, as it provides built-in support for it. In particular:
- It has both a parameterless constructor and one that takes an
IMessenger
instance, to be used with dependency injection. It also exposes aMessenger
property that can be used to send and receive messages in the viewmodel. If the parameterless constructor is used, theMessenger.Default
instance will be assigned to theMessenger
property. - It exposes an
IsActive
property to activate/deactivate the viewmodel. In this context, to "activate" means that a given viewmodel is marked as being in use, such that eg. it will start listening for registered messages, perform other setup operations, etc. There are two related methods,OnActivated
andOnDeactivated
, that are invoked when the property changes value. By default,OnDeactivated
automatically unregisters the current instance from all registered messages. For best results and to avoid memory leaks, it's recommended to useOnActivated
to register to messages, and to useOnDeactivated
to do cleanup operations. This pattern allows a viewmodel to be enabled/disabled multiple times, while being safe to collect without the risk of memory leaks every time it's deactivated. By default,OnActived
will automatically register all the message handlers defined through theIRecipient<TMessage>
interface. - It exposes a
Broadcast<T>(T, T, string)
method which sends aPropertyChangedMessage<T>
message through theIMessenger
instance available from theMessenger
property. This can be used to easily broadcast changes in the properties of a viewmodel without having to manually retrieve aMessenger
instance to use. This method is used by the overload of the variousSetProperty
methods, which have an additionalbool broadcast
property to indicate whether or not to also send a message.
Here's an example of a viewmodel that receives LoggedInUserRequestMessage
messages when active:
public class MyViewModel : ObservableRecipient, IRecipient<LoggedInUserRequestMessage>
{
public void Receive(LoggedInUserRequestMessage message)
{
// Handle the message here
}
}
In the above, OnActivated
automatically registers the instance as a recipient for LoggedInUserRequestMessage
messages, using that method as the action to invoke. Using the IRecipient<TMessage>
interface is not mandatory, and the registration can also be done manually (even using just an inline lambda expression):
public class MyViewModel : ObservableRecipient
{
protected override void OnActivated()
{
// Using a method group...
Messenger.Register<LoggedInUserRequestMessage>(this, Receive);
// ...or a lambda expression
Messenger.Register<LoggedInUserRequestMessage>(this, m =>
{
// Handle the message here
});
}
private void Receive(LoggedInUserRequestMessage message)
{
// Handle the message here
}
}
There is a small mistake in the RequestValue() method. It initializes a LoadingTask which I think should be the RequestTask.