Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
68837a4
Added introduction docs (and dupe toc.md)
Sergio0694 Jul 18, 2020
8e4176f
Added ObservableObject docs
Sergio0694 Jul 18, 2020
23f514f
Added RelayCommand docs
Sergio0694 Jul 18, 2020
1fb5af0
Minor fixes
Sergio0694 Jul 18, 2020
767b938
Added AsyncRelayCommand docs
Sergio0694 Jul 18, 2020
138e489
Added Ioc docs
Sergio0694 Jul 18, 2020
cb39fb5
Added Messenger docs
Sergio0694 Jul 18, 2020
276729b
Added ObservableRecipient docs
Sergio0694 Jul 18, 2020
db4945c
Added C#/VB.NET as language options
Sergio0694 Jul 25, 2020
d7d91b9
Apply initial suggestions with no follow up
Sergio0694 Jul 25, 2020
8546839
Fixed incorrectly named IAsyncRelayCommand property
Sergio0694 Jul 25, 2020
06c1c69
Minor formatting tweaks
Sergio0694 Jul 25, 2020
3ed5e87
Removed unnecessary setup step, fixed a typo
Sergio0694 Jul 25, 2020
ec8f88c
Renamed a section
Sergio0694 Jul 25, 2020
adc9afc
Added example scenario for messages
Sergio0694 Jul 25, 2020
0a72a80
Updated API names
Sergio0694 Jul 25, 2020
69b4c5b
Fixed leftover API rename
Sergio0694 Jul 25, 2020
f1d3cc3
Added note for the IsActive property
Sergio0694 Jul 26, 2020
dcd922b
Updated introduction bullet point
Sergio0694 Jul 27, 2020
b8b1cb4
Removed redundant line
Sergio0694 Jul 27, 2020
fcb0ace
Improved phrasing of uni test paragraphs
Sergio0694 Jul 27, 2020
2aba1cb
Improved description for SetPropertyAndNotifyOnCompletion
Sergio0694 Jul 27, 2020
3dc9451
Added Inversion of Control link to Wikipedia
Sergio0694 Jul 27, 2020
780b4cc
Fixed AsyncRelayCommand sample intro
Sergio0694 Jul 27, 2020
687cb07
Fixed a typo
Sergio0694 Jul 28, 2020
1048027
Improved notes about memory leaks with messages
Sergio0694 Jul 28, 2020
4027d23
Updated the Task.ResultOrDefault to sync with PR
Sergio0694 Jul 30, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions docs/mvvm/AsyncRelayCommand.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
title: AsyncRelayCommand
author: Sergio0694
description: An asynchronous command whose sole purpose is to relay its functionality to other objects by invoking delegates
keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, mvvm, componentmodel, property changed, notification, binding, command, delegate, net core, net standard
dev_langs:
- csharp
---

# AsyncRelayCommand and AsyncRelayCommand<T>

The [`AsyncRelayCommand`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.input.AsyncRelayCommand) and [`AsyncRelayCommand<T>`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.input.AsyncRelayCommand-1) 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:

```csharp
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:

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

## Sample Code

There are more examples in the [unit tests](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/UnitTests/UnitTests.Shared/Mvvm).

## Requirements

| Device family | Universal, 10.0.16299.0 or higher |
| --- | --- |
| Namespace | Microsoft.Toolkit.Mvvm |
| NuGet package | [Microsoft.Toolkit.Mvvm](https://www.nuget.org/packages/Microsoft.Toolkit.Mvvm/) |

## API

* [AsyncRelayCommand source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand.cs)
* [AsyncRelayCommand&lt;T> source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.Mvvm/Input/AsyncRelayCommand{T}.cs)
70 changes: 70 additions & 0 deletions docs/mvvm/Introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
title: Introduction to the MVVM package
author: Sergio0694
description: An overview of how to get started with MVVM package and to the APIs it contains
keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, get started, visual studio, MVVM, net core, net standard
dev_langs:
- csharp
- vb
---

# Introduction to the MVVM package

The `Microsoft.Toolkit.Mvvm` package is a modern, fast, and modular MVVM library. It is part of the Windows Community Toolkit and is built around the following principles:

- **Platform and Runtime Independent** - **.NET Standard 2.0** 🚀 (UI Framework Agnostic)
- **Simple to pick-up and use** - No strict requirements on Application structure or coding-paradigms (outside of 'MVVM'ness), i.e., flexible usage.
- **À la carte** - Freedom to choose which components to use.
- **Reference Implementation** - Lean and performant, providing implementations for interfaces that are included in the Base Class Library, but lack concrete types to use them directly.

This package targets .NET Standard so it can be used on any app platform: UWP, WinForms, WPF, Xamarin, Uno, and more; and on any runtime: .NET Native, .NET Core, .NET Framework, or Mono. It runs on all of them. The API surface is identical in all cases, making it perfect for building shared libraries.

To install the package from within Visual Studio:

1. In Solution Explorer, right-click on the project and select **Manage NuGet Packages**. Search for **Microsoft.Toolkit.Mvvm** and install it.

![NuGet Packages](../resources/images/ManageNugetPackages.png "Manage NuGet Packages Image")

2. Add a using or Imports directive to use the new APIs:

```c#
using Microsoft.Toolkit.Mvvm;
```
```vb
Imports Microsoft.Toolkit.Mvvm
```

3. Code samples are available in the other docs pages for the MVVM package, and in the [unit tests](https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/UnitTests/UnitTests.Shared/Mvvm) for the project.

## When should I use this package?

Use this package for access to a collection of standard, self-contained, lightweight types that provide a starting implementation for building modern apps using the MVVM pattern. These types alone are usually enough for many users to build apps without needing additional external references.

The included types are:

- **Microsoft.Toolkit.Mvvm.ComponentModel**
- `ObservableObject`
- `ObservableRecipient`
- **Microsoft.Toolkit.Mvvm.DependencyInjection**
- `Ioc`
- **Microsoft.Toolkit.Mvvm.Input**
- `RelayCommand`
- `RelayCommand<T>`
- `AsyncRelayCommand`
- `AsyncRelayCommand<T>`
- `IRelayCommand`
- `IRelayCommand<in T>`
- `IAsyncRelayCommand`
- `IAsyncRelayCommand<in T>`
- **Microsoft.Toolkit.Mvvm.Messaging**
- `Messenger`
- `IMessenger`
- **Microsoft.Toolkit.Mvvm.Messaging.Messages**
- `PropertyChangedMessage<T>`
- `RequestMessage<T>`
- `AsyncRequestMessage<T>`
- `CollectionRequestMessage<T>`
- `AsyncCollectionRequestMessage<T>`
- `ValueChangedMessage<T>`

This package aims to offer as much flexibility as possible, so developers are free to choose which components to use. All types are loosely-coupled, so that it's only necessary to include what you use. There is no requirement to go "all-in" with a specific series of all-encompassing APIs, nor is there a set of mandatory patterns that need to be followed when building apps using these helpers. Combine these building blocks in a way that best fits your needs.
89 changes: 89 additions & 0 deletions docs/mvvm/Ioc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
title: Ioc
author: Sergio0694
description: A type that facilitates the use of the IServiceProvider type
keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, mvvm, service, dependency injection, net core, net standard
dev_langs:
- csharp
---

# Ioc ([Inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control))

The [`Ioc`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.DependencyInjection.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:

```csharp
// 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:

```csharp
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:

```csharp
// 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!

## More docs

For more info about `Microsoft.Extensions.DependencyInjection`, see [here](https://docs.microsoft.com/aspnet/core/fundamentals/dependency-injection).

## Sample Code

There are more examples in the [unit tests](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/UnitTests/UnitTests.Shared/Mvvm).

## Requirements

| Device family | Universal, 10.0.16299.0 or higher |
| --- | --- |
| Namespace | Microsoft.Toolkit.Mvvm |
| NuGet package | [Microsoft.Toolkit.Mvvm](https://www.nuget.org/packages/Microsoft.Toolkit.Mvvm/) |

## API

* [ObservableObject source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs)
120 changes: 120 additions & 0 deletions docs/mvvm/Messenger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
---
title: Messenger
author: Sergio0694
description: A type that can be used to exchange messages between different objects
keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, mvvm, service, messenger, messaging, net core, net standard
dev_langs:
- csharp
---

# Messenger

The [`Messenger`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.Messaging.Messenger) class (with the accompanying [`IMessenger`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.Messaging.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:

```csharp
// 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 =>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to indicate examples of where in the code the calls to Messenger.Default. could be made.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added an example scenario in adc9afc, let me know if it needs tweaking! 👍

{
// 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:

```csharp
// 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`](ObservableRecipient.md), 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:

```csharp
// 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:

```csharp
// Create a message
public class LoggedInUserRequestMessage : AsyncRequestMessage<User>
{
}

// Register the receiver in a module
Messenger.Default.Register<LoggedInUserRequestMessage>(this, m =>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like an async method that isn't using the await and async keywords.
If this is correct can it be called out.

Copy link
Member Author

@Sergio0694 Sergio0694 Jul 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method is actually synchronous (you can't have an asynchronous method at all if you don't use the await keyword), as all the message handlers are just Action<T> (they'd need to instead be Func<T, Task> of some sort otherwise, in order to be awaited). What's happening here is just that we're invoking an asynchronous method (that GetCurrentUserAsync) and replying to the message with the task returned by that. But the message handler itself is still perfectly synchronous - the original sender of the request message, which is awaiting the reply, will not be awaiting the request message itself (which is synchronously sent and received), but only the task used by the message receiver to reply to the request.

Let me know if that helps, and/or if you feel like we should add a note with more info to clarify this 😊

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern is that someone will see GetCurrentUserAsync() and think, that's an async method it should be awaited.

Does Reply need to be passed a Task or can it be passed a concrete value?
What if someone wanted to make an async call in the Action/lambda and then do something with that before replying?

There's currently lots of potential for uncertainty here.

Copy link
Member Author

@Sergio0694 Sergio0694 Jul 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Reply method in AsyncRequestMessage<T> has two overloads, one taking T and one taking Task<T>. The main thing is, it doesn't really matter that a method is asynchronous or not, the thing users should care about is only the return type. You could have a Task<T>-returning method that is synchronous, you wouldn't (and shouldn't) care. Even more so because the async part is not even part of the method prototype, it's purely an implementation detail.

The point for users here should just be that you can reply to async request messages with a Task<T> you can get from somewhere. If you want to run some asynchronous method (which would return a Task<T>) you still wouldn't need to await it. You'd just invoke it and reply directly (as in, synchronously), with the returned Task<T> instance.

I totally get your point though and we might need to add some notes on this, I feel like this is a point over which many developers get confused pretty often. As in, they see any Task<T>-returning method and immediately just assume they have to use await, when that's not really the case at all. To make an example:

"What if someone wanted to make an async call in the Action/lambda and then do something with that before replying?"

Let's imagine you had some IRestService.GetValueAsync() method you wanted to call, which returns a Task<T>.
The only difference would be that instead of doing:

Messenger.Default.Register<ValueAsyncRequestMessage>(this, async m =>
{
    m.Reply(await IRestService.GetValueAsync());
});

You'd instead skip the extra async state machine (which would also add unnecessary overhead in this case) and do:

Messenger.Default.Register<ValueAsyncRequestMessage>(this, m =>
{
    m.Reply(RestService.GetValueAsync());
});

It's exactly the same - the only differenceis that in this case the only await operation would be in the message sender on the returned request message, skipping the "proxy" one in the actual message handler. I should note that this is purely an implementation detail though - there's no practical functionality change because of this. The same exact functionality is still possible, it's just a matter of how it's used, or specifically, of how you'd write asynchronous message listeners for async request messages.

Let me know if this helps, and how you think we should clarify this in the docs! 😊

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

## Sample Code

There are more examples in the [unit tests](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/UnitTests/UnitTests.Shared/Mvvm).

## Requirements

| Device family | Universal, 10.0.16299.0 or higher |
| --- | --- |
| Namespace | Microsoft.Toolkit.Mvvm |
| NuGet package | [Microsoft.Toolkit.Mvvm](https://www.nuget.org/packages/Microsoft.Toolkit.Mvvm/) |

## API

* [Messenger source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.Mvvm/Messaging/Messenger.cs)
* [IMessenger source code](https://github.com/Microsoft/WindowsCommunityToolkit//blob/master/Microsoft.Toolkit.Mvvm/Messaging/IMessenger.cs)
Loading