CommunityToolkit.Mvvm Messenger
Messagerie Pub/sub pour ViewModels (ou n'importe quels objets) sans forcer un graphe de références partagées. Fait partie de CommunityToolkit.Mvvm 8.x.
TL;DR. Utilisez par défaut
WeakReferenceMessenger.Default. Enregistrez les gestionnaires avec le lambda(recipient, message)et le modificateurstaticpour ne jamais capturerthis. Héritez deObservableRecipientet basculezIsActiveà l'activation/désactivation pour obtenir l'enregistrement/désenregistrement automatique.
Quand utiliser cette skill
- Deux ou plusieurs ViewModels doivent réagir à un événement (login, changement de thème, sauvegarde, navigation) sans se référencer mutuellement
- Un ViewModel doit demander une valeur à un autre VM (requête/réponse)
- Vous délimitez les événements à un sous-système ou une fenêtre avec des tokens de canal
- Diagnostiquer les problèmes « mon gestionnaire ne s'exécute jamais » ou de durée de vie des destinataires avec références faibles
Pour les générateurs de source, les classes de base et les commandes, consultez la skill mvvm-toolkit. Pour le câblage d'injection de dépendances (enregistrement d'une instance IMessenger), consultez mvvm-toolkit-di.
Choisir une implémentation
| Type | Quand |
|---|---|
WeakReferenceMessenger.Default |
Par défaut. Les destinataires sont tenus faiblement — éligibles au GC même s'ils sont enregistrés. Le nettoyage interne s'exécute lors des GC complets ; aucun Cleanup() manuel nécessaire. |
StrongReferenceMessenger.Default |
Le profileur montre que le messenger est chaud et l'allocation importe. Les destinataires sont épinglés jusqu'à ce que vous appeliez Unregister. Oublier la désinscription les fuit. |
Instance IMessenger personnalisée |
Par fenêtre/par portée (p. ex., un messenger par fenêtre d'application). Construisez directement, injectez via l'injection de dépendances. |
Le constructeur sans paramètres de ObservableRecipient utilise WeakReferenceMessenger.Default. Passez un IMessenger différent à son constructeur pour le remplacer.
Définir un message
La boîte à outils fournit des classes de base ; n'importe quelle classe fonctionne.
using CommunityToolkit.Mvvm.Messaging.Messages;
// Diffusion avec un seul payload
public sealed class LoggedInUserChangedMessage(User user)
: ValueChangedMessage<User>(user);
// Forme personnalisée (les records sont parfaits pour cela)
public sealed record ThemeChangedMessage(AppTheme NewTheme);
// Signal vide
public sealed record RefreshRequestedMessage;
Enregistrer un destinataire
Style lambda (recommandé)
WeakReferenceMessenger.Default.Register<MyViewModel, ThemeChangedMessage>(
this,
static (recipient, message) => recipient.OnThemeChanged(message.NewTheme));
Le modificateur static empêche l'allocation accidentelle de fermeture et garde this en dehors du lambda — utilisez le paramètre recipient à la place.
Style interface IRecipient<TMessage>
public sealed class MyViewModel : ObservableRecipient,
IRecipient<ThemeChangedMessage>,
IRecipient<RefreshRequestedMessage>
{
public void Receive(ThemeChangedMessage message) { /* ... */ }
public void Receive(RefreshRequestedMessage message) { /* ... */ }
}
ObservableRecipient.OnActivated() appelle Messenger.RegisterAll(this), qui souscrit à chaque interface IRecipient<T> implémentée par le type. Si vous n'utilisez pas ObservableRecipient, enregistrez manuellement :
WeakReferenceMessenger.Default.RegisterAll(this);
Envoyer un message
WeakReferenceMessenger.Default.Send(new ThemeChangedMessage(AppTheme.Dark));
// Les payloads vides utilisent la surcharge sans paramètres :
WeakReferenceMessenger.Default.Send<RefreshRequestedMessage>();
Canaux (tokens)
Délimitez les messages à un sous-système ou une fenêtre avec un token (n'importe quelle valeur équitable — int, string, Guid) :
const int LeftPaneChannel = 1;
WeakReferenceMessenger.Default.Register<MyViewModel, RefreshRequestedMessage, int>(
this, LeftPaneChannel,
static (r, _) => r.RefreshLeft());
WeakReferenceMessenger.Default.Send(new RefreshRequestedMessage(), LeftPaneChannel);
Les messages envoyés sans token utilisent le canal partagé par défaut — ils ne sont pas livrés aux destinataires délimités par canal.
Requête / réponse
Pour les scénarios de type demande où un destinataire fournit une valeur à l'expéditeur, utilisez la famille RequestMessage<T>.
Requête synchrone
public sealed class CurrentUserRequest : RequestMessage<User> { }
WeakReferenceMessenger.Default.Register<UserService, CurrentUserRequest>(
this,
static (r, m) => m.Reply(r.CurrentUser));
User user = WeakReferenceMessenger.Default.Send<CurrentUserRequest>();
La conversion implicite de CurrentUserRequest en User lève une exception si aucun destinataire n'a appelé Reply. Capturez le message pour vérifier d'abord :
var request = WeakReferenceMessenger.Default.Send<CurrentUserRequest>();
if (request.HasReceivedResponse)
User user = request.Response;
Requête asynchrone
public sealed class CurrentUserRequest : AsyncRequestMessage<User> { }
WeakReferenceMessenger.Default.Register<UserService, CurrentUserRequest>(
this,
static (r, m) => m.Reply(r.GetCurrentUserAsync()));
User user = await WeakReferenceMessenger.Default.Send<CurrentUserRequest>();
Requêtes de collections (fan-in)
CollectionRequestMessage<T> et AsyncCollectionRequestMessage<T> collectent une Reply de chaque destinataire répondant :
public sealed class OpenDocumentsRequest : CollectionRequestMessage<Document> { }
var docs = WeakReferenceMessenger.Default.Send<OpenDocumentsRequest>();
foreach (Document doc in docs) { /* ... */ }
Cycle de vie
Même avec WeakReferenceMessenger, désenregistrez explicitement quand un destinataire est démoli — cela supprime les entrées mortes et améliore les performances :
WeakReferenceMessenger.Default.Unregister<ThemeChangedMessage>(this);
WeakReferenceMessenger.Default.Unregister<ThemeChangedMessage, int>(this, LeftPaneChannel);
WeakReferenceMessenger.Default.UnregisterAll(this);
ObservableRecipient.OnDeactivated() le fait automatiquement quand IsActive bascule à false. Définissez-le à partir de votre hook d'activation :
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
ViewModel.IsActive = true;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
ViewModel.IsActive = false;
base.OnNavigatedFrom(e);
}
Pièges courants
- Capturer
thisdans le lambda.(r, m) => OnX(m)capture implicitementthis; alloue une fermeture et confond la durée de vie. Utilisez toujours(r, m) => r.OnX(m)avecstatic. - Destinataires avec références fortes sans
Unregister. AvecStrongReferenceMessenger, les destinataires (et tout leur graphe d'objets) restent épinglés pour toujours. Héritez soit deObservableRecipient(désenregistrement automatique dansOnDeactivated) soit appelezUnregisterAll(this). - Types de messages hérités. Un gestionnaire enregistré pour
BaseMessagen'est pas invoqué pourDerivedMessage : BaseMessage. Enregistrez chaque type concret. - Mauvaise instance de messenger. Envoyer via
WeakReferenceMessenger.Defaultet enregistrer via un messenger injecté par fenêtre signifie que le message n'arrive jamais. Utilisez le mêmeIMessengerpartout (généralement injectez-le viaObservableRecipient(messenger)). OnActivatedne s'exécute jamais.ObservableRecipientn'enregistre les gestionnairesIRecipient<T>que quandIsActivebascule defalseàtrue.- Mises à jour multi-threads. Le messenger est indépendant des threads. Si un gestionnaire met à jour l'interface utilisateur, organisez manuellement (
DispatcherQueue.TryEnqueue/Dispatcher.BeginInvoke).
Messengers multiples (délimitation par fenêtre)
services.AddSingleton<IMessenger>(WeakReferenceMessenger.Default); // à l'échelle de l'app
services.AddScoped<WindowScopedMessenger>(); // par fenêtre
Injectez le IMessenger approprié dans le constructeur du ViewModel :
public sealed partial class WindowViewModel(IMessenger messenger)
: ObservableRecipient(messenger) { }
Cela isole les diffusions à une seule fenêtre — utile pour les applications de bureau multi-fenêtres (WinUI 3, WPF, MAUI desktop, Avalonia).
Références
| Sujet | Fichier |
|---|---|
| Plongée approfondie complète (plus d'exemples de canaux/cycle de vie, diagnostics) | references/messenger-patterns.md |
Externe :
- Documentation Messenger : https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/messenger
- API
WeakReferenceMessenger: https://learn.microsoft.com/en-us/dotnet/api/communitytoolkit.mvvm.messaging.weakreferencemessenger - Source : https://github.com/CommunityToolkit/dotnet