Skip to content

Instantly share code, notes, and snippets.

@Sergio0694
Last active February 26, 2021 19:58
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Sergio0694/987209d0855837ee6835f6e758f5cf40 to your computer and use it in GitHub Desktop.
Save Sergio0694/987209d0855837ee6835f6e758f5cf40 to your computer and use it in GitHub Desktop.
Draft docs for the Microsoft.Toolkit.Mvvm package

Microsoft.Toolkit.Mvvm package

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 the INotifyPropertyChanging and INotifyPropertyChanged interfaces.
  • Microsoft.Toolkit.Mvvm.DependencyInjection: contains helper types to easily setup dependency injection into an application, by leveraging the Microsoft.Extensions.DependencyInjection package.
  • Microsoft.Toolkit.Mvvm.Input: contains ICommand 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.

Table of Contents

ObservableObject

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.

How it works

ObservableObject has the following main features:

  • It provides a base implementation for INotifyPropertyChanged and INotifyPropertyChanging, exposing the PropertyChanged and PropertyChanging events.
  • It provides a series of SetProperty methods that can be used to easily set property values from types inheriting from ObservableObject, and to automatically raise the appropriate events.
  • It provides the SetPropertyAndNotifyOnCompletion method, which is analogous to SetProperty but with the ability to set Task properties and raise the notification events automatically when the assigned tasks are completed.
  • It exposes the OnPropertyChanged and OnPropertyChanging methods, which can be overridden in derived types to customize how the notification events are raised.

Simple property

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.

Wrapping a non-observable model

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.

Handling Task<T> properties

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.

RelayCommand and RelayCommand<T>

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.

How they work

RelayCommand and RelayCommand<T> have the following main features:

  • They provide a base implementation of the ICommand interface.
  • They also implement the IRelayCommand (and IRelayCommand<T>) interface, which exposes a NotifyCanExecuteChanged method to raise the CanExecuteChanged event.
  • They expose constructors taking delegates like Action and Func<T>, which allow the wrapping of standard methods and lambda expressions.

Working with ICommand

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.

AsyncRelayCommand and AsyncRelayCommand<T>

The AsyncRelayCommand and AsyncRelayCommand<T> are ICommand implementations that extend the functionalities offered by RelayCommand, with support for asynchronous operations.

How they work

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 an IsRunning 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 and IAsyncRelayCommand<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.

Working with asynchronous commands

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).

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.

Configure and resolve services

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.

Constructor injection

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!

Messenger

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.

How it works

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).

Sending and receiving messages

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).

Using request messages

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>();

ObservableRecipient

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.

How it works

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 a Messenger property that can be used to send and receive messages in the viewmodel. If the parameterless constructor is used, the Messenger.Default instance will be assigned to the Messenger 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 and OnDeactivated, 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 use OnActivated to register to messages, and to use OnDeactivated 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 the IRecipient<TMessage> interface.
  • It exposes a Broadcast<T>(T, T, string) method which sends a PropertyChangedMessage<T> message through the IMessenger instance available from the Messenger property. This can be used to easily broadcast changes in the properties of a viewmodel without having to manually retrieve a Messenger instance to use. This method is used by the overload of the various SetProperty methods, which have an additional bool 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
    }
}
@sonnemaf
Copy link

sonnemaf commented Aug 3, 2020

There is a small mistake in the RequestValue() method. It initializes a LoadingTask which I think should be the RequestTask.

public class MyModel : ObservableObject
{
    private Task<int> requestTask;

    public Task<int> RequestTask
    {
        get => requestTask;
        set => SetAndNotifyOnCompletion(ref requestTask, () => requestTask, value);
    }

    public void RequestValue()
    {
        RequestTask = WebService.LoadMyValueAsync();
    }
}

@Sergio0694
Copy link
Author

@sonnemaf Whoops, that was indeed a typo, thanks! 😊

@ILMTitan
Copy link

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...
}

This part seems a bit confused. The class is ConsoleLogger, but the constructor is FileLogger. The interface is ILogger, but the comment says it implements IFileLogger.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment