File: Model\NotificationService.cs
Web Access
Project: src\src\Aspire.Dashboard\Aspire.Dashboard.csproj (Aspire.Dashboard)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
namespace Aspire.Dashboard.Model;
 
/// <summary>
/// Thread-safe singleton implementation of <see cref="INotificationService"/>.
/// </summary>
internal sealed class NotificationService(TimeProvider timeProvider) : INotificationService
{
    private const int MaxNotifications = 100;
 
    private readonly object _lock = new();
    private readonly List<(string Id, NotificationEntry Entry)> _notifications = [];
    private int _unreadCount;
 
    public int UnreadCount
    {
        get
        {
            lock (_lock)
            {
                return _unreadCount;
            }
        }
    }
 
    public event Action? OnChange;
 
    public IReadOnlyList<NotificationMessage> GetNotifications()
    {
        lock (_lock)
        {
            // Return a snapshot, most recent first.
            var result = new NotificationMessage[_notifications.Count];
            for (var i = 0; i < _notifications.Count; i++)
            {
                var item = _notifications[_notifications.Count - 1 - i];
                result[i] = new NotificationMessage { Id = item.Id, Entry = item.Entry };
            }
 
            return result;
        }
    }
 
    public string AddNotification(NotificationEntry notification)
    {
        notification.Timestamp = timeProvider.GetUtcNow();
        var id = Guid.NewGuid().ToString("N");
        lock (_lock)
        {
            _notifications.Add((id, notification));
            _unreadCount++;
 
            // Remove oldest notifications when the limit is exceeded.
            while (_notifications.Count > MaxNotifications)
            {
                _notifications.RemoveAt(0);
            }
        }
 
        OnChange?.Invoke();
        return id;
    }
 
    public void ReplaceNotification(string id, NotificationEntry notification)
    {
        notification.Timestamp = timeProvider.GetUtcNow();
        lock (_lock)
        {
            for (var i = 0; i < _notifications.Count; i++)
            {
                if (_notifications[i].Id == id)
                {
                    _notifications[i] = (id, notification);
                    break;
                }
            }
        }
 
        OnChange?.Invoke();
    }
 
    public void RemoveNotification(string id)
    {
        lock (_lock)
        {
            _notifications.RemoveAll(n => n.Id == id);
        }
 
        OnChange?.Invoke();
    }
 
    public void ClearAll()
    {
        lock (_lock)
        {
            _notifications.Clear();
            _unreadCount = 0;
        }
 
        OnChange?.Invoke();
    }
 
    public void ResetUnreadCount()
    {
        lock (_lock)
        {
            _unreadCount = 0;
        }
 
        OnChange?.Invoke();
    }
}