Messager

L’interface IMessenger constitue un contrat pour les types pouvant être utilisés pour échanger des messages entre différents objets. Cet élément peut être utilise pour dissocier plusieurs modules d’une application sans devoir conserver de fortes références aux types référencés. Il est également possible d’envoyer des messages vers des canaux spécifiques, uniquement identifiés par un jeton, et de disposer de messagers différents dans diverses sections d’une application. Le Kit d’outils MVVM fournit deux implémentations prêtes à l’emploi : WeakReferenceMessenger et StrongReferenceMessenger : le premier utilise des références faibles en interne et offre une gestion automatique de la mémoire aux destinataires, alors que le dernier utilise des références fortes et exige que les développeurs désinscrivent manuellement leurs destinataires lorsqu’ils ne sont plus nécessaires (vous trouverez ci-dessous d’autres informations sur la façon de désinscrire des gestionnaires de messages) mais, en échange, offre de meilleures performances et une utilisation beaucoup plus faible de la mémoire.

API de plateforme : IMessenger, WeakReferenceMessenger, StrongReferenceMessenger, IRecipient<TMessage>, MessageHandler<TRecipient, TMessage>, ObservableRecipient, RequestMessage<T>, AsyncRequestMessage<T>, CollectionRequestMessage<T>, AsyncCollectionRequestMessage<T>.

Fonctionnement

Les types implémentant un IMessenger sont chargés du maintien des liens entre les destinataires (récepteurs de messages) et leurs types de message enregistrés, avec les gestionnaires de messages respectifs. Tout objet peut être inscrit comme destinataire d’un type de message donné en utilisant un gestionnaire de messages qui peut être appelé quand l’instance IMessenger est utilisée pour envoyer un message de ce type. Il est également possible d’envoyer des messages via des canaux de communication spécifiques (chacun identifié par un jeton unique) afin que plusieurs modules puissent échanger des messages du même type sans entraîner de conflit. Les messages envoyés sans jeton utilisent le canal partagé par défaut.

Il existe deux moyens pour effectuer une inscription de message : via l’interface IRecipient<TMessage> ou en tirant parti d’un délégué de MessageHandler<TRecipient, TMessage> qui agit en tant que gestionnaire de messages. Le premier vous permet d’inscrire tous les gestionnaires de message à l’aide d’un seul appel à l’extension RegisterAll qui inscrit automatiquement les destinataires de tous les gestionnaires de messages déclarés, tandis que le dernier est utile si vous avez besoin de davantage de flexibilité ou quand vous souhaitez utiliser une expression lambda simple comme gestionnaire.

WeakReferenceMessenger et StrongReferenceMessenger exposent également une propriété Default qui offre une implémentation thread-safe intégrée au package. Il est également possible de créer plusieurs instances de messager, le cas échéant. Par exemple, si une instance différente est injectée avec un fournisseur de service d’injection de dépendances dans un autre module de l’application (entre autres, plusieurs fenêtres s’exécutant dans le même processus).

Remarque

Du fait de la simplicité d’utilisation du type WeakReferenceMessenger et qu’il correspond au comportement du type de messager de la bibliothèque MvvmLight, il constitue le type par défaut utilisé par le type ObservableRecipient dans le Kit d’outils MVVM. Vous pouvez toujours utiliser le StrongReferenceType en transmettant une instance au constructeur de cette classe.

Envoi et réception des messages

Tenez compte des éléments suivants :

// Create a message
public class LoggedInUserChangedMessage : ValueChangedMessage<User>
{
    public LoggedInUserChangedMessage(User user) : base(user)
    {        
    }
}

// Register a message in some module
WeakReferenceMessenger.Default.Register<LoggedInUserChangedMessage>(this, (r, m) =>
{
    // Handle the message here, with r being the recipient and m being the
    // input message. Using the recipient passed as input makes it so that
    // the lambda expression doesn't capture "this", improving performance.
});

// Send a message from some other module
WeakReferenceMessenger.Default.Send(new LoggedInUserChangedMessage(user));

Imaginons que le type de message est utilisé dans une application de messagerie simple qui affiche un en-tête avec le nom d’utilisateur et l’image de profil de l’utilisateur actuellement connecté, un panneau avec une liste des conversations et un autre panneau avec les messages de la conversation actuelle, si l’une d’entre elles est sélectionnée. Supposons que ces trois sections soient prises en charge par les types HeaderViewModel, ConversationsListViewModel et ConversationViewModel respectivement. Dans ce scénario, il est possible que le message LoggedInUserChangedMessage soit envoyé par le HeaderViewModel après la fin d’une opération de connexion et ces autres viewmodels (vues modèle) peuvent inscrire des gestionnaires de messages pour celle-ci. Par exemple, ConversationsListViewModel va charger une liste de conversations pour le nouvel utilisateur et ConversationViewModel va simplement fermer la conversation présente actuelle, le cas échéant.

L’instance IMessenger se charge de la remise des messages à tous les destinataires inscrits. Notez qu’un destinataire peut s’inscrire aux messages d’un type spécifique. Notez que les types de message hérités ne sont pas inscrits dans les implémentations IMessenger par défaut fournies par le Kit d’outils MVVM.

Quand un destinataire n’est plus nécessaire, vous devez le désinscrire afin qu’il cesse de recevoir des messages. Vous pouvez désinscrire par type de message, par jeton d’inscription ou par destinataire :

// Unregisters the recipient from a message type
WeakReferenceMessenger.Default.Unregister<LoggedInUserChangedMessage>(this);

// Unregisters the recipient from a message type in a specified channel
WeakReferenceMessenger.Default.Unregister<LoggedInUserChangedMessage, int>(this, 42);

// Unregister the recipient from all messages, across all channels
WeakReferenceMessenger.Default.UnregisterAll(this);

Avertissement

Comme mentionné précédemment, ce n’est pas strictement nécessaire lorsque vous utilisez le type WeakReferenceMessenger, car il utilise des références faibles pour suivre les destinataires, ce qui signifie que les destinataires inutilisés sont toujours éligibles pour le nettoyage de la mémoire bien qu’ils aient encore des gestionnaires de messages actifs. Il est toujours conseillé de les désinscrire pour améliorer les performances. D’autre part, l’implémentation de StrongReferenceMessenger utilise des références fortes pour suivre les destinataires inscrits. Cette opération est effectuée pour des raisons de performances et elle signifie que vous devez désinscrire manuellement chaque destinataire inscrit pour éviter des fuites de mémoire. Autrement dit, aussi longtemps que le destinataire est inscrit, l’instance StrongReferenceMessenger utilisée y conserve une référence active et le récupérateur de mémoire ne peut donc pas collecter cette instance. Vous pouvez gérer ceci manuellement ou vous pouvez hériter de ObservableRecipient qui, une fois désactivé, se charge automatiquement par défaut de supprimer toutes les inscriptions de messages pour un destinataire (consultez les documents sur ObservableRecipient pour obtenir plus d’informations à ce sujet).

Il est également possible d’utiliser l’interface IRecipient<TMessage> pour inscrire des gestionnaires de messages. Dans ce cas, chaque destinataire doit implémenter l’interface pour un type de message donné et fournir une méthode Receive(TMessage) qui va être appelée lors de la réception de messages, comme suit :

// Create a message
public class MyRecipient : IRecipient<LoggedInUserChangedMessage>
{
    public void Receive(LoggedInUserChangedMessage message)
    {
        // Handle the message here...   
    }
}

// Register that specific message...
WeakReferenceMessenger.Default.Register<LoggedInUserChangedMessage>(this);

// ...or alternatively, register all declared handlers
WeakReferenceMessenger.Default.RegisterAll(this);

// Send a message from some other module
WeakReferenceMessenger.Default.Send(new LoggedInUserChangedMessage(user));

Utilisation de messages de demande

Une autre fonctionnalité utile des instances de messager est que vous pouvez également les utiliser pour demander des valeurs d’un module à un autre. Pour ce faire, le package inclut une classe racine RequestMessage<T> que vous pouvez utiliser comme suit :

// Create a message
public class LoggedInUserRequestMessage : RequestMessage<User>
{
}

// Register the receiver in a module
WeakReferenceMessenger.Default.Register<MyViewModel, LoggedInUserRequestMessage>(this, (r, m) =>
{
    // Assume that "CurrentUser" is a private member in our viewmodel.
    // As before, we're accessing it through the recipient passed as
    // input to the handler, to avoid capturing "this" in the delegate.
    m.Reply(r.CurrentUser);
});

// Request the value from another module
User user = WeakReferenceMessenger.Default.Send<LoggedInUserRequestMessage>();

La classe RequestMessage<T> comprend un convertisseur implicite qui effectue la conversion d’un LoggedInUserRequestMessage vers son objet User possible contenu. Il vérifie également la réception d’une réponse pour le message et lève une exception si ce n’est pas le cas. Il est également possible d’envoyer des messages de demande sans cette garantie de réponse obligatoire : stockez simplement le message retourné dans une variable locale, puis vérifiez de manière manuelle si une valeur de réponse est disponible ou non. L’exception automatique n’est pas levée en cas de non réception d’une réponse lors du retour de la méthode Send.

Le même espace de noms inclut également un message des demandes de base pour d’autres scénarios : AsyncRequestMessage<T>, CollectionRequestMessage<T> et AsyncCollectionRequestMessage<T>. Voici comment vous pouvez utiliser un message de demande asynchrone :

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

// Register the receiver in a module
WeakReferenceMessenger.Default.Register<MyViewModel, LoggedInUserRequestMessage>(this, (r, m) =>
{
    m.Reply(r.GetCurrentUserAsync()); // We're replying with a Task<User>
});

// Request the value from another module (we can directly await on the request)
User user = await WeakReferenceMessenger.Default.Send<LoggedInUserRequestMessage>();

Exemples

  • Consultez l’exemple d’application (pour plusieurs infrastructures d’interface utilisateur) afin de voir le Kit d’outils Modèle-vue-vue modèle (MVVM) en action.
  • Vous trouverez également d’autres exemples dans les tests unitaires.