Messaging Between View Models

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

Two view models that must react to each other create a design problem. If BooksViewModel holds a reference to BookEditorViewModel (or the reverse), the two types are welded together, neither can be tested alone, and the lifetime of one decides the lifetime of the other. Messaging removes the reference: a sender hands a message object to a shared messenger, and every recipient that registered for that message type receives it. Neither side knows the other exists; both only agree on the message class. This page covers the messenger in the MVVM Toolkit (CommunityToolkit.Mvvm), which is the messaging mechanism Microsoft documents for MVVM on .NET. It was written against .NET 10 LTS (latest patch 10.0.13) with C# 14, Visual Studio 2026 18.11.1, Windows App SDK 2.5.1 and CommunityToolkit.Mvvm 8.4.2 on Windows 10 and Windows 11. .NET 11 is at release candidate 2 (11.0.0-rc.2) and general availability is expected at .NET Conf, 10 to 13 November 2026; the documentation for the messenger does not list any change for it. The messenger has no dependency on a UI framework, so everything here is the same on WPF, WinUI 3 and Windows Forms, apart from the threading notes.

The running scenario is Bookshelf Desktop. When the user saves a book in the editor, the list in BooksViewModel must refresh, and no view model may reference the other. The page builds that, then adds request messages (asking another view model for a value), channels (tokens) and the lifetime rules that decide whether messaging leaks memory. For the pattern itself see The MVVM Pattern, and for the base classes and source generators see The MVVM Toolkit; this page assumes both.

Why a messenger, and when not to use one

The .NET Multi-platform App UI architecture guide explains the motivation. Plain .NET events already implement publish-subscribe, and they are the simplest choice when loose coupling is not needed (a control and the page that contains it). Their cost is that the subscriber type must reference the publisher type, and the lifetimes are tied together: a long-lived or static publisher keeps every subscriber that did not unsubscribe alive, which prevents or delays garbage collection of the subscriber. A messenger breaks both couplings. The guide also lists the alternatives that often fit better than messaging:

  • Data binding and property change notification already carry a view model’s state to its view.

  • Passing data during navigation is the direct way for one view model to hand a value to the next (see Navigation, Dialogs & Services in MVVM).

  • A shared service registered in dependency injection (an IBookRepository, a store object) is explicit and easy to follow; two view models can both depend on it.

Messaging trades explicitness for decoupling. The sender cannot see who reacts, a message that nobody receives is silently dropped, and a debugger shows no call from sender to recipient. Use it for notifications that are genuinely "something happened, whoever cares may react" (a book was saved, the user logged out, the theme changed), and use a service or navigation parameter when exactly one known collaborator needs the data.

The messenger types

The contract is IMessenger in the CommunityToolkit.Mvvm.Messaging namespace. The package supplies two implementations, and both expose a thread-safe Default instance (WeakReferenceMessenger.Default, StrongReferenceMessenger.Default).

Aspect WeakReferenceMessenger StrongReferenceMessenger

References to recipients

Weak. An unused recipient stays eligible for garbage collection even if it still has registered handlers.

Strong. While a recipient is registered the messenger keeps it alive; each recipient must be unregistered manually.

Performance and memory

Slower and uses more memory than the strong one.

Better performance and far less memory usage.

Cleanup needed

Not required for correctness; still good practice, to improve performance.

Required, otherwise the recipient leaks.

Typical fit

Subscribers without a clearly defined lifecycle (the guide’s wording).

A workflow with a tightly controlled lifetime, such as a subscription tied to a page appearing and disappearing.

Default of ObservableRecipient

Yes (WeakReferenceMessenger.Default when the parameterless constructor is used).

Only if you pass an instance to the constructor.

Source: Messenger

The documentation states that the weak messenger is simpler to use and matches the behaviour of the messenger in the older MvvmLight library, which is why it is the default. You can also create more than one messenger instance, for example one per module or per window group, and inject it through the service provider.

Members of IMessenger

Member Purpose

Register<TRecipient,TMessage,TToken>(recipient, token, handler)

The single primitive registration. All the convenience overloads call it.

Send<TMessage,TToken>(message, token)

Delivers a message to every recipient registered for that message type on that channel.

Unregister<TMessage,TToken>(recipient, token)

Removes one message type on one channel for a recipient.

UnregisterAll(recipient)

Removes a recipient from all messages on all channels.

UnregisterAll<TToken>(recipient, token)

Removes a recipient from all messages on one channel.

IsRegistered<TMessage,TToken>(recipient, token)

Reports whether the recipient is registered for the message.

Cleanup()

Trims the internal data structures. It does not unregister anything.

Reset()

Unregisters all recipients. Useful between unit tests that share a messenger.

The everyday overloads are extension methods in IMessengerExtensions: Register<TMessage>(recipient) for an IRecipient<TMessage>, RegisterAll(recipient) and RegisterAll(recipient, token), Register<TRecipient,TMessage>(recipient, handler), Send<TMessage>(message), Send<TMessage>() and Unregister<TMessage>(recipient). There is also CreateObservable<TMessage>(), which exposes a message type as an IObservable<T> for Rx-style code.

Defining a message

A message is an ordinary class, defined by what happened and what data goes with it. The toolkit supplies base classes for the common shapes; ValueChangedMessage<T> carries a single value in its Value property. For Bookshelf Desktop the "book saved" fact is a message holding the saved Book:

using CommunityToolkit.Mvvm.Messaging.Messages;
using Bookshelf.Core;

namespace Bookshelf.Desktop.Messages;

/// <summary>Sent after a book was added or updated in the repository.</summary>
public sealed class BookSavedMessage(Book book) : ValueChangedMessage<Book>(book);

/// <summary>Sent after a book was deleted from the repository.</summary>
public sealed class BookDeletedMessage(int id) : ValueChangedMessage<int>(id);

The guide’s own example defines AddProductMessage : ValueChangedMessage<int> with a constructor that passes the value to the base class; the primary-constructor form above is the C# 12 and later spelling of the same idea. Three rules keep messages healthy:

  • Immutable payloads. The guide warns that several threads can read the payload at once, so the handler must not modify it. Book is already an immutable positional record, which is the reason Bookshelf Desktop uses it.

  • One fact per class. Subscribers register for a specific type. The default messengers do not deliver inherited message types, so a handler registered for a base class never receives a derived message.

  • Name it as an event in the past tense (BookSavedMessage) for notifications, and as a question (SelectedBookRequestMessage) for requests.

Sending and receiving: the book-saved flow

The editor saves through the repository and then announces it. The recipient is the list view model.

sequenceDiagram actor U as User participant E as BookEditorViewModel participant R as IBookRepository participant M as IMessenger participant L as BooksViewModel participant V as Books list (view) U->>E: SaveCommand E->>R: UpdateAsync(book) R-->>E: completed E->>M: Send(new BookSavedMessage(book)) M->>L: Receive(message) for each registered recipient L->>L: replace or add the BookViewModel with that Id L-->>V: ObservableCollection raises CollectionChanged V-->>U: the row shows the new values

The figure shows that the editor never touches BooksViewModel. The messenger looks up every recipient registered for BookSavedMessage and calls its handler on the sending thread, so by the time Send returns every handler has run.

The sender

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Bookshelf.Core;
using Bookshelf.Desktop.Messages;

namespace Bookshelf.Desktop.ViewModels;

// Excerpt: the canonical editor of the MVVM Toolkit page, BookEditorViewModel(Book book), with the repository and
// the messenger added as constructor parameters. Its other properties and validation members are omitted.
public sealed partial class BookEditorViewModel(
    Book book, IBookRepository repository, IMessenger messenger) : ObservableValidator
{
    [ObservableProperty]
    public partial string Title { get; set; } = book.Title;

    [ObservableProperty]
    public partial string Author { get; set; } = book.Author;

    public Book ToBook() => book with { Title = Title, Author = Author };

    [RelayCommand]
    private async Task SaveAsync(CancellationToken cancellationToken)
    {
        ValidateAllProperties();
        if (HasErrors)
        {
            return;
        }

        var saved = ToBook();
        if (saved.Id == 0)
        {
            saved = await repository.AddAsync(saved, cancellationToken);
        }
        else
        {
            await repository.UpdateAsync(saved, cancellationToken);
        }

        messenger.Send(new BookSavedMessage(saved));
    }
}

Source: Messenger

The messenger is injected as an IMessenger rather than read from WeakReferenceMessenger.Default, so a unit test can pass a fresh instance. Send is fire-and-forget: it also succeeds when nobody is registered, in which case the message is ignored.

The recipient as an ObservableRecipient

ObservableRecipient extends ObservableObject and is the base class meant for view models that use the messenger. Implementing IRecipient<TMessage> for each message type is the declarative way to declare handlers.

using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Bookshelf.Core;
using Bookshelf.Desktop.Messages;

namespace Bookshelf.Desktop.ViewModels;

// Messenger-aware variant of BooksViewModel: the base class becomes ObservableRecipient
// and the messenger is injected as a third constructor argument.
public sealed partial class BooksViewModel(
    IBookRepository repository, IDialogService dialogs, IMessenger messenger)
    : ObservableRecipient(messenger),
      IRecipient<BookSavedMessage>,
      IRecipient<BookDeletedMessage>
{
    public ObservableCollection<BookViewModel> Books { get; } = [];

    [ObservableProperty]
    public partial BookViewModel? SelectedBook { get; set; }

    [ObservableProperty]
    public partial string StatusMessage { get; set; } = string.Empty;

    // Called for every BookSavedMessage while this view model is active.
    public void Receive(BookSavedMessage message)
    {
        var saved = message.Value;
        var existing = Books.FirstOrDefault(b => b.Id == saved.Id);
        if (existing is null)
        {
            Books.Add(new BookViewModel(saved));
            StatusMessage = $"Added {saved.Title}";
            return;
        }

        Books[Books.IndexOf(existing)] = new BookViewModel(saved);
        StatusMessage = $"Updated {saved.Title}";
    }

    public void Receive(BookDeletedMessage message)
    {
        var existing = Books.FirstOrDefault(b => b.Id == message.Value);
        if (existing is not null)
        {
            Books.Remove(existing);
        }
    }

    [RelayCommand(IncludeCancelCommand = true)]
    private async Task LoadAsync(CancellationToken cancellationToken)
    {
        Books.Clear();
        foreach (var book in await repository.GetAllAsync(cancellationToken))
        {
            Books.Add(new BookViewModel(book));
        }
    }
}

Replacing the element at the same index preserves the position; re-selecting is the view’s concern. Because BookViewModel wraps an immutable Book, replacing the wrapper is the correct way to show a new value. The sample assumes IsFavorite and other view-only state are re-read from the repository or carried over in a real application.

Activation: when does the registration happen

Constructing the view model does not register it. ObservableRecipient exposes IsActive; setting it to true calls OnActivated(), which by default registers every IRecipient<TMessage> the class implements, and setting it to false calls OnDeactivated(), which by default unregisters the instance from all messages. The documentation recommends registering in OnActivated and cleaning up in OnDeactivated, because this lets the same view model be enabled and disabled any number of times and still be safe to collect.

using System.Windows;

namespace Bookshelf.Desktop.Views;

public partial class BooksView : System.Windows.Controls.UserControl
{
    public BooksView(ViewModels.BooksViewModel viewModel)
    {
        InitializeComponent();
        DataContext = viewModel;
        Loaded += (_, _) => viewModel.IsActive = true;
        Unloaded += (_, _) => viewModel.IsActive = false;
    }
}

The activation call is the view’s job (or the navigation service’s, see Navigation, Dialogs & Services in MVVM), so that the view model stays free of UI types. The documentation does not prescribe which UI event should drive it. In WPF, Loaded can fire more than once for an element that is removed and added back to the tree, for example when a tab is switched, and Unloaded does not run in every shutdown path; setting IsActive to its current value changes nothing, so the pairing above is safe to repeat.

Registering by hand

IRecipient<T> is not mandatory. A view model can register a lambda or a method group in OnActivated. Taking the recipient as the first lambda parameter, and not touching this, is the documented way to avoid capturing the instance and to improve performance.

protected override void OnActivated()
{
    // Default channel, lambda that does not capture "this".
    Messenger.Register<BooksViewModel, BookSavedMessage>(this, (recipient, message) =>
    {
        recipient.StatusMessage = $"Saved {message.Value.Title}";
    });

    // Method group: a static method with the (recipient, message) signature.
    Messenger.Register<BooksViewModel, BookDeletedMessage>(this, OnBookDeleted);
}

private static void OnBookDeleted(BooksViewModel recipient, BookDeletedMessage message)
{
    var existing = recipient.Books.FirstOrDefault(b => b.Id == message.Value);
    if (existing is not null)
    {
        recipient.Books.Remove(existing);
    }
}

Source: Messenger

If you override OnActivated and still want the IRecipient<T> handlers registered, call base.OnActivated(). That base call is what performs the automatic registration (documented behaviour: "by default OnActivated will automatically register all the message handlers defined through the IRecipient<TMessage> interface").

Request messages

Messages can also ask. A request message carries a reply slot; a recipient fills it with Reply, and the sender reads the answer from the value returned by Send. This is request-response between modules without a reference, with the same caveat as any messaging: there must be a recipient.

Base class Use

RequestMessage<T>

One synchronous reply of type T. Send<TMessage>() returns the message, which converts implicitly to T.

AsyncRequestMessage<T>

One reply that is a T or a Task<T>. The returned message is awaitable.

CollectionRequestMessage<T>

Any number of replies, enumerated by the sender.

AsyncCollectionRequestMessage<T>

Any number of replies, some of them tasks, enumerated asynchronously.

Synchronous request

using CommunityToolkit.Mvvm.Messaging.Messages;
using Bookshelf.Core;

namespace Bookshelf.Desktop.Messages;

/// <summary>Asks whichever view model owns the list for the currently selected book.</summary>
public sealed class SelectedBookRequestMessage : RequestMessage<Book?>;

Source: Messenger

// In BooksViewModel.OnActivated (or any active recipient):
Messenger.Register<BooksViewModel, SelectedBookRequestMessage>(this, (recipient, message) =>
{
    message.Reply(recipient.SelectedBook?.Model);
});

// In a status bar view model that holds an IMessenger:
Book? selected = messenger.Send<SelectedBookRequestMessage>();

Source: Messenger

RequestMessage<T> has an implicit conversion to T. The conversion throws if no recipient replied, so a request with no active recipient fails loudly instead of returning a default. To avoid the exception, store the returned message in a local variable and test whether a response is available before reading it. The page for the messenger does not name the exception type or the members used for that check; read the API reference for RequestMessage<T>.

Asynchronous request

An async request lets the recipient do I/O before it answers. Here the editor asks the shell whether it may close without losing work; the shell shows a confirmation dialog.

public sealed class ConfirmCloseRequestMessage : AsyncRequestMessage<bool>;

// Recipient: a ShellViewModel whose ConfirmDiscardAsync method shows the dialog.
Messenger.Register<ShellViewModel, ConfirmCloseRequestMessage>(this, (recipient, message) =>
{
    message.Reply(recipient.ConfirmDiscardAsync()); // returns Task<bool>
});

// Sender: the request message is awaitable.
bool mayClose = await messenger.Send<ConfirmCloseRequestMessage>();

Source: Messenger

The documented example replies with a Task<User> and the sender awaits the message directly. For a result that one view model computes for another over many steps, prefer a service: a request message hides the call target and cannot express cancellation.

Tokens and channels

Messages sent with no token travel on the default shared channel. A token identifies a private channel, so two modules can exchange messages of the same type without receiving each other’s. The token type parameter is constrained to IEquatable<TToken>, so a string, an int, a Guid or a readonly record struct works; an enum does not satisfy the constraint and fails to compile. Registrations and sends that use different tokens never meet.

/// <summary>String tokens that name the messaging channels of each shelf.</summary>
public static class ShelfChannels
{
    public const string Library = "library";
    public const string Wishlist = "wishlist";
}

// Receive only the wishlist shelf's saves.
Messenger.Register<WishlistViewModel, BookSavedMessage, string>(
    this, ShelfChannels.Wishlist, (recipient, message) => recipient.Add(message.Value));

// Send to that channel only.
messenger.Send(new BookSavedMessage(book), ShelfChannels.Wishlist);

// Unregister the message type on that channel, or everything on that channel.
messenger.Unregister<BookSavedMessage, string>(this, ShelfChannels.Wishlist);
messenger.UnregisterAll(this, ShelfChannels.Wishlist);

// Register every IRecipient<T> the class implements on a channel.
messenger.RegisterAll(this, ShelfChannels.Wishlist);

Source: Messenger

A message sent on a token is not delivered to recipients registered on the default channel, and the reverse. The Unregister calls above follow the same rule: UnregisterAll(recipient) clears every channel, and the token overload only one. Another way to separate modules is to use several messenger instances: for example one per window, injected through the service provider (see Dependency Injection & the Generic Host). Channels are the lighter tool; separate instances give stronger isolation and make Reset() cheap in tests.

Broadcasting property changes

ObservableRecipient can announce that one of its properties changed, using PropertyChangedMessage<T>. Its Broadcast<T>(oldValue, newValue, propertyName) method sends the message through the instance’s Messenger. The SetProperty overloads that take a bool broadcast argument call it for you, so a view model can opt in per property (SetProperty(ref field, value, true)). Other view models register for PropertyChangedMessage<T> with the concrete T they care about, and read the sender, the property name and the old and new values from the message.

This sits beside, not in place of, normal binding: the view still binds to the property directly. Use broadcast only when another view model needs the change. For generated properties, the [NotifyPropertyChangedRecipients] attribute does the same: on a type that inherits from ObservableRecipient, the generated setter calls Broadcast(oldValue, value) after a successful change (see the MVVM Toolkit (CommunityToolkit.Mvvm) page).

[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial Book? SelectedBook { get; set; }

Threading

The messenger does no marshalling. A message is delivered synchronously, on the thread that called Send. The MAUI architecture guide says this explicitly: messages are published from the thread the sender is running on and subscribers receive them on the same thread, and a subscriber that updates the UI from a message sent on a background thread must marshal to the UI thread itself.

Stack How a recipient marshals to the UI thread Notes

WPF

Application.Current.Dispatcher.InvokeAsync(...) or a dispatcher abstraction.

ObservableCollection<T> changes must happen on the UI thread unless collection synchronization is enabled.

WinUI 3

DispatcherQueue.TryEnqueue(...).

The queue belongs to the window’s thread; get it from a control or DispatcherQueue.GetForCurrentThread() on the UI thread.

Windows Forms

Control.Invoke / BeginInvoke, or a captured SynchronizationContext.

Not applicable when Send is called from event handlers on the UI thread.

.NET MAUI (for comparison)

Dispatcher.DispatchAsync(...) as in the guide, or MainThread.BeginInvokeOnMainThread.

The guide marks MessagingCenter as no longer recommended; the toolkit messenger replaces it.

The simplest rule is to send from the UI thread, which is what the editor above does: after await in a command, WPF and WinUI 3 continue on the UI thread, so Send runs there. If a service sends from a background thread, wrap the update in the recipient. The details of the dispatcher types are in Threading, Dispatcher & Async.

Leaks and lifetime

Whether messaging leaks memory is decided by who holds the reference to the recipient.

flowchart TD A[Recipient registered with an IMessenger] --> B{Which implementation} B -->|WeakReferenceMessenger| C[Messenger holds a weak reference] B -->|StrongReferenceMessenger| D[Messenger holds a strong reference] C --> E[Recipient can be collected once nothing else references it] C --> F[Handlers keep running until it is collected] D --> G[Recipient stays alive until it is unregistered] G --> H[Unregister, UnregisterAll or OnDeactivated]

With the weak messenger a forgotten recipient is eventually collected. Between becoming unreachable and being collected it is still registered, so it can keep reacting to messages, which is a correctness problem (a closed editor that still writes to a status bar) even though it is not a leak. With the strong messenger the messenger keeps every registered recipient alive for as long as it exists. Since Default is a static instance, a view model that never unregisters lives for the whole process.

The practical rules:

  • Prefer the activation pattern. Register in OnActivated, let OnDeactivated unregister, and set IsActive from the view’s Loaded and Unloaded (or the navigation service). This is the documented recommendation, and it keeps both messengers correct.

  • Use the strong messenger only with a controlled lifetime (a page that appears and disappears), and then always deactivate. If you pass StrongReferenceMessenger.Default to ObservableRecipient, the base class still unregisters for you on deactivation.

  • Do not hand-roll the same thing with a plain += event on a long-lived object. That is the situation the guide describes: the subscriber is kept alive by the publisher’s reference.

  • Call Cleanup() rarely. It only trims the messenger’s internal structures; it does not unregister anyone.

  • Do not register a recipient twice for the same message and channel. The toolkit documents IsRegistered for checking, and registering the same recipient twice for one message and channel is not a supported way to replace a handler. The documentation for the messenger does not state what happens, so check with IsRegistered instead of relying on it.

Source: Messenger

The messenger in each stack

Topic WPF WinUI 3 Windows Forms

Package

CommunityToolkit.Mvvm

CommunityToolkit.Mvvm

CommunityToolkit.Mvvm

Where the activation call goes

Loaded / Unloaded of the view, or the navigation service

Loaded / Unloaded of the page, or OnNavigatedTo / OnNavigatedFrom

Form Load / FormClosed, or the presenter

UI-thread marshalling

Dispatcher

DispatcherQueue

SynchronizationContext / Control.Invoke

Message and view-model code

Identical

Identical

Identical (if you use a presenter instead of binding)

The same BookSavedMessage therefore works unchanged in a WPF main window, a WinUI 3 page and a Windows Forms form that uses a presenter, which is a practical benefit of keeping messages in a UI-free assembly such as Bookshelf.Core or a Messages folder of the view-model project. The WinUI 3 guidance on data binding and MVVM names the toolkit as Microsoft’s recommended MVVM library; it does not add messenger-specific guidance.

What changed over time

Version Messaging change

.NET Framework with MvvmLight

Messenger.Default was the messenger of the third-party MvvmLight library. The toolkit’s weak messenger is described as matching its behaviour, which eases migration.

.NET MAUI (before the toolkit messenger)

MessagingCenter was the built-in static messaging class. The architecture guide now marks it as no longer recommended in favour of the toolkit messenger.

MVVM Toolkit 7 (as Microsoft.Toolkit.Mvvm)

IMessenger, both implementations and ObservableRecipient existed; the documentation’s API links still use the old microsoft.toolkit.mvvm namespace in places.

MVVM Toolkit 8.x

Namespaces are CommunityToolkit.Mvvm.*. Version 8.4 supports the partial-property form of [ObservableProperty] used in the examples. The messenger API surface shown here is the one listed in the 8.4 API reference.

.NET 8, 9, 10, 11

No messaging change is documented. The toolkit is a separate NuGet package, not a part of the framework.

Pitfalls

  • Forgetting to activate. An ObservableRecipient receives nothing until IsActive is true; a view model that is constructed but never activated looks like "the message was not sent".

  • Registering in the constructor with the strong messenger. The registration outlives the view model unless something unregisters it. Register in OnActivated.

  • Inherited message types. Registering for a base type does not receive derived messages in the default messengers.

  • Mutable payloads. Several recipients read the same object; mutate a copy, or use immutable records.

  • Sending from a background thread to a UI-bound recipient. The handler runs on the sender’s thread. Marshal in the recipient, or send from the UI thread.

  • A request with no recipient. Converting a RequestMessage<T> to T throws when nobody replied. Check first, or guarantee a recipient is active.

  • Hidden dependencies. A view model whose behaviour depends on a message from nowhere is harder to understand. Keep message classes in one folder, name them as events or requests, and document who sends and who receives.

  • Using Default in tests. Tests that share WeakReferenceMessenger.Default can see each other’s recipients. Create new WeakReferenceMessenger() per test and inject it, or call Reset().

  • Treating messaging as a mediator. The messenger does not order handlers, retry, persist or return results from many recipients except through Collection request messages. If you need those, use a service or a dedicated library; third-party mediator and event-aggregator libraries exist but are outside Microsoft documentation.

  • Deadlocks by blocking on an async request. Awaiting Send is the supported form; calling .Result on the returned message from the UI thread blocks the dispatcher.

What the documentation does not provide

  • The messenger and ObservableRecipient pages are the toolkit’s conceptual pages; the Learn text has not been rewritten for the 8.4 release, and its code samples omit using directives. The namespaces used here are CommunityToolkit.Mvvm.Messaging and CommunityToolkit.Mvvm.Messaging.Messages.

  • There is no documented guidance on message ordering, handler exceptions or re-entrancy (a handler that sends another message). Test your own application.

  • There is no documented benchmark for the weak versus strong messengers beyond the qualitative statement of better performance and lower memory for the strong one. Measure before choosing it for performance reasons.

  • Which UI event should set IsActive is not specified; the Loaded / Unloaded pairing, the navigation service hook and the message classes in this page are patterns of this guide, not official samples. The Bookshelf types are original.

  • AsyncRequestMessage<T> details (a second reply, what happens with no recipient) are not described on the messenger page; the exception behaviour stated for RequestMessage<T> is the only one documented there.

  • No documentation covers messaging in WPF or WinUI 3 specifically. The MAUI chapter is the only Microsoft guide with a worked messenger example, and it targets .NET MAUI.

References