Notifications utilisateur
Les notifications utilisateur Lagoon permettent d'envoyer des messages persistants à un ou plusieurs utilisateurs, de les synchroniser en temps réel avec SignalR et de les conserver côté navigateur dans IndexedDB pour rester consultables hors connexion. L'interface est fournie par le module séparé Lagoon.UI.Notifications, distribué via le package NuGet Lagoon.UI.Notifications.
Les composants publics du module sont exposés dans Lagoon.UI.Components (LgNotificationMenuItem, LgNotificationItem). Le compteur simple LgNotificationCounter est dans Lagoon.UI.Components.Internal. Le namespace n'est donc pas Lagoon.UI.Notifications.Components dans les sources actuelles.
Les messages de confirmation (ShowConfirm) et les toastr (ShowInformation, ShowError, ShowWarning, ShowSuccess) restent des fonctionnalités générales de Lagoon.UI; ils ne dépendent pas du module de notifications persistantes décrit ici.
Prérequis & installation
Modèle de données
Dans le projet Model, créez une entité de notification qui hérite de NotificationBase.
public class Notification : NotificationBase
{
public string Category { get; set; }
}
NotificationBase apporte les champs Id, Title, Description et CreationDate. L'association utilisateur / notification est portée par NotificationUserWithUser<Notification, ApplicationUser> pour les utilisateurs à clé string, ou NotificationUserWithUserGuid<Notification, ApplicationUser> pour les utilisateurs à clé Guid; elle contient notamment Id, NotificationId, UserId, IsRead et UpdateDate.
Déclarez le stockage dans le DbContext applicatif avec UseNotifications<TNotification>.
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Ajoute les tables de notifications et d'association utilisateur.
UseNotifications<Notification>(builder);
}
Après l'ajout de l'entité et de l'appel à UseNotifications<Notification>, créez une migration EF Core pour générer les tables Notifications et NotificationUsers (ou les noms personnalisés passés à UseNotifications).
ViewModel partagé
Dans le projet Shared, créez un ViewModel qui hérite de NotificationVmBase.
public class NotificationVm : NotificationVmBase
{
public string Category { get; set; }
}
NotificationVmBase contient Title, Description, CreationDate, UpdateDate, TargetId, NotificationId, NotificationUserId et IsRead. Le tri par défaut est décroissant sur CreationDate; surchargez CompareTo dans le ViewModel pour un ordre différent.
Enregistrement côté client
Ajoutez le package NuGet Lagoon.UI.Notifications au projet Client, puis enregistrez le service de notifications.
builder.Services.AddNotification<NotificationVm>();
Cet appel enregistre INotificationService<NotificationVm> et INotificationService. Il ne peut être appelé qu'une seule fois dans l'application cliente.
Enregistrement côté serveur
Créez un manager serveur. Utilisez LgNotificationManager<TUser, TVmNotification, TNotification> pour les utilisateurs à clé string, ou LgNotificationManagerGuid<TUser, TVmNotification, TNotification> pour les utilisateurs à clé Guid.
public class NotificationManager : LgNotificationManager<ApplicationUser, NotificationVm, Notification>
{
public NotificationManager(ApplicationDbContext db, IHubContext<NotificationHub> hub)
: base(db, hub)
{ }
protected override void MapViewModel(NotificationVm vmNotification, Notification notification)
{
// Copie les champs métier ajoutés sur l'entité vers le ViewModel client.
vmNotification.Category = notification.Category;
}
}
Enregistrez le manager dans les services serveur.
services.AddNotification<NotificationManager>();
AddNotification<TNotificationManager> active SignalR, le protocole MessagePack, le fournisseur d'identifiant utilisateur et le hub NotificationHub. Le hub est mappé automatiquement par MapLagoonFallback lorsque le service est présent.
Exposez ensuite le contrôleur de synchronisation.
[ApiController]
public class NotificationController : LgNotificationBaseController<Notification, NotificationVm>
{
public NotificationController(ILgNotificationManager<NotificationVm, Notification> notificationManager)
: base(notificationManager)
{ }
}
Intégration dans le menu
Ajoutez un ou plusieurs menus de notifications dans la configuration du menu. Le composant recommandé est LgNotificationMenuItem<TItem>.
<LgMenuSet Id="grpNotifications">
<AuthorizeView Policy="@AppPolicies.CanReadNotifications">
<LgNotificationMenuItem TItem="NotificationVm"
Text="#Notifications"
IconName="@IconNames.All.BellFill"
OnNotificationClick="OnNotificationClickAsync"
ItemIconName="GetNotificationIconName"
ItemCssClass="GetNotificationCssClass" />
</AuthorizeView>
</LgMenuSet>
public partial class MenuConfiguration
{
// Choisit l'icône de chaque notification en fonction de ses données métier.
private string GetNotificationIconName(NotificationVm notification)
{
return IconNames.All.BellFill;
}
// Ajoute une classe CSS pour permettre un rendu différencié par thème applicatif.
private string GetNotificationCssClass(NotificationVm notification)
{
return notification.IsRead ? "notification-read" : "notification-unread";
}
// Réagit au clic sur une notification sans modifier automatiquement son état de lecture.
private Task OnNotificationClickAsync(NotificationEventArgs<NotificationVm> args)
{
return Task.CompletedTask;
}
}
Pour afficher plusieurs flux, utilisez FilterPredicate. Par exemple, un menu peut ne montrer que les notifications d'une catégorie donnée, tandis qu'un autre affiche les tâches ou alertes.
<LgNotificationMenuItem TItem="NotificationVm"
Text="#MailNotifications"
IconName="@IconNames.All.BellFill"
FilterPredicate="IsMailNotification" />
public partial class MenuConfiguration
{
// Conserve uniquement les notifications de la catégorie courrier.
private bool IsMailNotification(NotificationVm notification)
{
return notification.Category == "mail";
}
}
LgNotificationCounter existe aussi comme compteur visuel minimal, mais il n'assure ni chargement ni synchronisation : il affiche uniquement la valeur passée dans Counter.
LgNotificationItem est le rendu unitaire utilisé par LgNotificationMenuItem. Il expose des paramètres publics, mais son usage normal est interne au menu; utilisez plutôt NotificationTemplate sur LgNotificationMenuItem pour personnaliser la carte de notification.
Côté serveur — envoyer / supprimer
Le service serveur à injecter est ILgNotificationManager<TVmNotification, TNotification>.
Signatures principales
| Méthode | Description |
|---|---|
Task SendNotificationAsync(TNotification notification, Guid userId) | Envoie une notification à un utilisateur. |
Task SendNotificationAsync(TNotification notification, IEnumerable<Guid> userIds) | Envoie une notification à plusieurs utilisateurs. |
Task DeleteNotificationAsync(Guid notificationId, Guid? ownerUserId) | Supprime une notification et toutes ses associations utilisateur. |
Task DeleteUserNotificationAsync(Guid notificationUserId, Guid? ownerUserId = null) | Supprime l'association d'une notification pour un utilisateur. |
Task DeleteUserNotificationAsync(List<Guid> notificationUserIds, Guid? ownerUserId = null) | Supprime plusieurs associations utilisateur. |
Task UpdateNotificationReadStateAsync(Guid notificationUserId, bool isRead, Guid userId) | Marque une notification comme lue ou non lue pour un utilisateur. |
Task UpdateNotificationReadStateAsync(List<Guid> notificationUserIds, bool isRead, DateTime? updateDate) | Marque plusieurs notifications comme lues ou non lues. |
[ApiController]
[Route("api/orders/{orderId:guid}/notifications")]
public class OrderNotificationController : LgControllerBase
{
private readonly ILgNotificationManager<NotificationVm, Notification> _notificationManager;
public OrderNotificationController(ILgNotificationManager<NotificationVm, Notification> notificationManager)
{
_notificationManager = notificationManager;
}
[HttpPost("send")]
public async Task<IActionResult> SendAsync(Guid orderId, Guid userId)
{
try
{
Notification notification = new()
{
Title = "#OrderNotificationTitle",
Description = "#OrderNotificationDescription",
Category = "order"
};
// Persiste la notification et déclenche le rafraîchissement SignalR du destinataire.
await _notificationManager.SendNotificationAsync(notification, userId);
return Ok();
}
catch (Exception ex)
{
return Problem(ex);
}
}
[HttpDelete("user-notifications/{notificationUserId:guid}")]
public async Task<IActionResult> DeleteForUserAsync(Guid notificationUserId)
{
try
{
// Limite la suppression aux notifications appartenant à l'utilisateur courant.
await _notificationManager.DeleteUserNotificationAsync(notificationUserId, ContextUserId);
return Ok();
}
catch (Exception ex)
{
return Problem(ex);
}
}
}
Contrat des endpoints internes
Le client INotificationService<TItem> appelle le contrôleur de base sur la route api/-/notification :
| Verbe | Route | Rôle |
|---|---|---|
GET | api/-/notification/GetNotifications | Charge les notifications de l'utilisateur courant. |
POST | api/-/notification/GetNotifications | Charge les notifications mises à jour depuis la date du dernier élément connu. |
GET | api/-/notification/UpdateNotificationReadState/{userNotificationId:guid}/{isRead} | Change l'état lu / non lu d'une notification. |
POST | api/-/notification/UpdateNotificationReadState/{isRead} | Change l'état lu / non lu d'une liste de notifications. |
POST | api/-/notification/SendPendingActions | Rejoue les actions effectuées hors connexion. |
DELETE | api/-/notification/DeleteUserNotification/{userNotificationId:guid} | Supprime une notification utilisateur. |
POST | api/-/notification/DeleteUserNotification | Supprime une liste de notifications utilisateur. |
Le hub SignalR utilisé pour les notifications est hubs/notification et requiert l'authentification.
Côté client — service & méthodes
Injectez INotificationService<NotificationVm> dans une page ou un composant. Appelez EnsureInitializedAsync avant de lire Notifications.
<LgButton Text="#RefreshNotifications"
IconName="@IconNames.All.ArrowClockwise"
OnClick="RefreshNotificationsAsync" />
public partial class NotificationsPage : LgPage
{
[Inject]
private INotificationService<NotificationVm> NotificationService { get; set; }
private IReadOnlyCollection<NotificationVm> Notifications { get; set; }
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
// Initialise IndexedDB, lance la connexion SignalR et prépare la collection locale.
await NotificationService.EnsureInitializedAsync();
NotificationService.OnNotificationChange += OnNotificationChange;
Notifications = NotificationService.Notifications;
}
private async Task RefreshNotificationsAsync(ActionEventArgs args)
{
try
{
// Force une synchronisation distante avant de relire la collection locale.
await NotificationService.EnsureInitializedAsync(true, args.CancellationToken);
Notifications = NotificationService.Notifications;
}
catch (Exception ex)
{
ShowException(ex);
}
}
private void OnNotificationChange()
{
Notifications = NotificationService.Notifications;
StateHasChanged();
}
}
Signatures côté client
| Méthode / membre | Description |
|---|---|
IReadOnlyCollection<TItem> Notifications | Collection locale chargée depuis IndexedDB. |
event Action OnNotificationChange | Événement levé quand la liste locale est mise à jour. |
Task EnsureInitializedAsync(bool fromRemote = false, CancellationToken cancellationToken = default) | Initialise le stockage local et le hub; avec fromRemote = true, force aussi la synchronisation serveur. |
Task RefreshNotifications() | Relit la collection locale et déclenche OnNotificationChange. |
Task SetReadStateItemAsync(TItem notification, bool isRead) | Marque une notification comme lue / non lue. |
Task SetReadStateItemsAsync(IEnumerable<Guid> notificationUserIds, bool isRead) | Marque plusieurs notifications comme lues / non lues. |
Task DeleteItemAsync(TItem notification) | Supprime une notification à partir du ViewModel. |
Task DeleteItemAsync(Guid notificationUserId) | Supprime une notification à partir de son identifiant utilisateur. |
Task DeleteItemsAsync(IEnumerable<Guid> notificationUserIds) | Supprime plusieurs notifications. |
En cas de perte réseau, les suppressions et changements d'état sont stockés en actions en attente dans IndexedDB, puis rejoués au serveur lors de la reconnexion SignalR.
Démonstration
Essayez les notifications dans le showcase Lagoon.