Open Closed

Real-time Notification System like AspNetZero #8927


User avatar
0
roberto.fiocchi created

Having received no response to the posts and emails sent, I open a dedicated ticket.
See my post https://abp.io/qa/questions/3052/3a18165e-11ab-1b69-892b-659fe71558de

Using Blazor Wasm (abp 8.3.2) I need to implement the real-time notification system already present in AspNetZero

I already read https://abp.io/docs/latest/framework/real-time/signalr, but it is not specific to Blazor WASM
I already read https://learn.microsoft.com/en-us/aspnet/core/blazor/tutorials/signalr-blazor?view=aspnetcore-8.0&tabs=visual-studio
and I tried to implement it but I have problems both with the Hub management and with the use of reusable Blazor components inside the Lepton-x toolbar (https://abp.io/support/questions/8800/Double-Initialization-of-Custom-Toolbar-Component)

Can you give me some information on when this feature will be available?
Or can you guide me step by step to implement this service?

Thanks
Roberto


12 Answer(s)
  • User Avatar
    0
    enisn created
    Support Team .NET Developer

    Can you give me some information on when this feature will be available?

    It seems a nice & fancy feature so far, but whenever I checked the roadmap of the ABP team, I can't see this feature soon in the planned milestones. However you can still implement it on your own.

    I already read https://abp.io/docs/latest/framework/real-time/signalr, but it is not specific to Blazor WASM

    ABP framework doesn't provide a real-time notification system, it's an application logic your project needs. And it can be different depending on the case. There are several approaches you can consider:

    1. Web Push Notifications: This approach uses the browser's Push API to deliver notifications even when the application is not active. It's great for engaging users who aren't actively using your app and works across different browsers and devices. However, it requires user permission and may not be supported in all browsers.

    2. SignalR/WebSocket: This real-time communication approach establishes a persistent connection between client and server, enabling instant message delivery. It's ideal for real-time notifications as it provides immediate updates with minimal latency. The main advantages are bi-directional communication and efficient server resource usage. However, it may face challenges with firewalls, proxy servers, and requires maintaining open connections.

    3. Polling Mechanism: This traditional approach involves the client periodically requesting updates from the server (e.g., every 30 seconds). It's simple to implement and works reliably across all environments. The downside is that it can be resource-intensive due to frequent server requests, may miss real-time updates between polls, and can impact server performance with many concurrent users.

    Given your utilization of Blazor WebAssembly within the .NET ecosystem, we recommend implementing SignalR as the optimal solution for real-time communication. This robust technology provides a seamless and efficient approach to WebSocket implementation with minimal complexity.

    and I tried to implement it but I have problems both with the Hub management and with the use of reusable Blazor components inside the Lepton-x toolbar (https://abp.io/support/questions/8800/Double-Initialization-of-Custom-Toolbar-Component)

    Sorry to see this, but it seems it's not a problem currently. There are 2 instances of a single component, and you can handle it easily by using another service that has single instance and inject it to the components. That service can be a singleton or a scoped service. (If you run your blazor app Blazor Server, it has to be scoped instead of singleton). That service can manage the hub connection and the notifications without dependency on a UI component.

  • User Avatar
    0
    roberto.fiocchi created

    Thanks for reply,
    if it was simple I would have already implemented it myself and I would not have opened the ticket nor insisted on requesting it as a feature request :-)
    It's an application logic that many web portals need, I hope it will be planned as soon as possible.
    It would be nice if the roadmap also included features for web applications and not just for microservices.

    Initially I would like to make something like this
    https://abp.io/support/questions/7286/How-to-add-notification-UI-in-angular-with-bell-icon-and-count
    and then extend it as a more complete service.

    In ABP documentation I found only these two documents
    https://abp.io/docs/latest/framework/ui/blazor/toolbars?#example-add-a-notification-icon

    https://abp.io/community/articles/realtime-notifications-via-signalr-in-abp-project-tsp2uqd3

    Can you provide me with a working example as ABP module (ABP version 8.3.2, Blazor WASM, EFCore) ?

    Thanks

  • User Avatar
    0
    enisn created
    Support Team .NET Developer

    I'll try to provide a really qucik example:

    • Let say you already have something similar at your server-side:

    [Authorize]
    public class NotificationHub : Hub
    {
        public async Task SendNotification(string message)
        {
            await Clients.All.SendAsync("ReceiveNotification", message);
        }
    }
    

    Make sure you have Volo.Abp.AspNetCore.SignalR package installed on your host (backend) project

    <PackageReference Include="Volo.Abp.AspNetCore.SignalR" Version="9.0.4" />
    
    [DependsOn(typeof(AbpAspNetCoreSignalRModule))]
    
    _I assume the following configurations are done:_
    
    ```csharp
    context.Services.AddSignalR();
    ```
    
    ```csharp
    app.UseConfiguredEndpoints(endpoints => 
    {
        endpoints.MapHub("/signalr-hubs/notification");
    });
    ```  
    
    • Now to to Blazor.Client project and make sure the following packages is installed and configured like below:

    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.0" />
    

    Implementing a shared service that handles events

    Now you're ready to implement a shared single instanced service that can be consumed from 2 different instances of notification component.

    NotificationService

    public class NotificationService : ISingletonDependency, IAsyncDisposable
    {
        private HubConnection _hubConnection;
        private readonly ILogger<NotificationService> _logger;
        private bool _isConnected;
    
        // Event that components can subscribe to
        public event Action<string> OnNotificationReceived;
    
        public NotificationService(
            ILogger<NotificationService> logger)
        {
            _logger = logger;
    
            _logger.LogInformation("NotificationService Created!");
        }
    
        public async Task InitializeAsync()
        {
           await InitializeHubConnection();
        }
    
        private async Task InitializeHubConnection()
        {
             if (_hubConnection != null)
            {
                return; // Already initialized, return immediately
            }
    
            try
            {
                // Build the hub URL
                var hubUrl = "https://localhost:44343".TrimEnd('/') + "/signalr-hubs/notification";
                
                _hubConnection = new HubConnectionBuilder()
                    .WithUrl(hubUrl, options =>
                    {
                        // Add token if needed
                        // options.AccessTokenProvider = () => Task.FromResult(_tokenProvider.GetAccessToken());
                    })
                    .WithAutomaticReconnect()
                    .Build();
    
                // Register for the ReceiveNotification event
                _hubConnection.On<string>("ReceiveNotification", (message) =>
                {
                    _logger.LogInformation($"Notification received: {message}");
                    OnNotificationReceived?.Invoke(message);
                });
    
                await _hubConnection.StartAsync();
                _isConnected = true;
                _logger.LogInformation("SignalR connection established");
            }
            catch (Exception ex)
            {
                _logger.LogError($"Error connecting to SignalR hub: {ex.Message}");
                _isConnected = false;
            }
        }
    
        public async ValueTask DisposeAsync()
        {
            if (_hubConnection != null)
            {
                await _hubConnection.DisposeAsync();
            }
        }
    }
    

    NotificationComponent.razor

    @namespace AbpSolution40.Blazor.Client.Components.Notification
    @using AbpSolution40.Blazor.Client.Services
    @using Microsoft.Extensions.Logging
    @using System.Collections.Generic
    @implements IDisposable
    @inject NotificationService NotificationService
    @inject ILogger<NotificationComponent> Logger
    
    
    <Dropdown>
        <DropdownToggle Color="Color.Primary">
        <button type="button" class="btn btn-outline-primary position-relative">
            <i class="fa fa-bell"></i>
            @if (Notifications.Count > 0)
            {
                <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
                    @Notifications.Count
                    <span class="visually-hidden">unread notifications</span>
                </span>
            }
        </button>
        </DropdownToggle>
        <DropdownMenu>
            @foreach (var notification in Notifications)
            {
                <DropdownItem>
                    <span>@notification.Message</span>
                    <small class="notification-time">@notification.Timestamp.ToString("g")</small>
                </DropdownItem>
            }
        </DropdownMenu>
    </Dropdown>
    
    @code {
        private List<NotificationItem> Notifications { get; set; } = new List<NotificationItem>();
        protected override async Task OnInitializedAsync()
        {
            await base.OnInitializedAsync();
            
            NotificationService.OnNotificationReceived += HandleNotificationReceived;
            await NotificationService.InitializeAsync();
            
            Logger.LogInformation("NotificationComponent initialized");
        }
        
        private void HandleNotificationReceived(string message)
        {
            var notification = new NotificationItem
            {
                Id = Guid.NewGuid(),
                Message = message,
                Timestamp = DateTime.Now
            };
            
            Notifications.Add(notification);
            StateHasChanged();
        }
        
        public void Dispose()
        {
            NotificationService.OnNotificationReceived -= HandleNotificationReceived;
        }
        
        public class NotificationItem
        {
            public Guid Id { get; set; }
            public string Message { get; set; } = string.Empty;
            public DateTime Timestamp { get; set; }
        }
    }          Id = Guid.NewGuid(),
                Message = message,
                Timestamp = DateTime.Now
            };
            
            Notifications.Add(notification);
            StateHasChanged();
        }
        
        private void ToggleNotificationsPopup()
        {
            _isPopupVisible = !_isPopupVisible;
        }
        
        private void RemoveNotification(NotificationItem notification)
        {
            Notifications.Remove(notification);
            StateHasChanged();
        }
        
        public void Dispose()
        {
            NotificationService.OnNotificationReceived -= HandleNotificationReceived;
        }
        
        public class NotificationItem
        {
            public Guid Id { get; set; }
            public string Message { get; set; } = string.Empty;
            public DateTime Timestamp { get; set; }
        }
    } 
    

    And of course I configured this component in my newly created Toolbar Contributor:

    public class AbpSolution40ToolbarContributor : IToolbarContributor
    {
        public Task ConfigureToolbarAsync(IToolbarConfigurationContext context)
        { 
            if (context.Toolbar.Name == StandardToolbars.Main)
            {
                context.Toolbar.Items.Add(new ToolbarItem(typeof(NotificationComponent)));
            }
            return Task.CompletedTask;
        }
    }
    

    And configured it in the ...ClientModule.cs

    Configure<AbpToolbarOptions>(options =>
    {
        options.Contributors.Add(new AbpSolution40ToolbarContributor());
    });
    

    It doesn't matter how many time the component is initialized. The NotificationService in the client-side is Singleton and it doesn't matter how many time it's resolved. There will be always single hub connection that listens the changes and delivers changes into component by using a C# Event named OnNotificationReceived.

    image.png

  • User Avatar
    0
    roberto.fiocchi created

    I try to create a solution and implement this code, but what should I put to set the correct url based on the environment?

    // Build the hub URL
    var hubUrl = "***https://localhost:44343***".TrimEnd('/') + "/signalr-hubs/notification";
    

    And to handle the token correctly, do I just uncomment the "options.AccessTokenProvider" line?

    _hubConnection = new HubConnectionBuilder()
                    .WithUrl(hubUrl, options =>
                    {
                        // Add token if needed
                        // options.AccessTokenProvider = () => Task.FromResult(_tokenProvider.GetAccessToken());
                    })
                    .WithAutomaticReconnect()
                    .Build();
    

    Thanks

  • User Avatar
    0
    enisn created
    Support Team .NET Developer

    This was just for demonstration but you can get it from the configuration by using RemoteServiceConfigurationProvider

    Inject it and use it and use GetConfigurationOrDefaultAsync method on the provider like this:

    var remoteServiceConfig = await _remoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync("Default");
    
  • User Avatar
    0
    roberto.fiocchi created

    I need a real working example.

    I don't know how to implement those two highlighted points in the correct way

    Thanks

  • User Avatar
    0
    enisn created
    Support Team .NET Developer

    Hi,

    It's a full-featured development that depened on some business deicions. Please start building on your own and ask the problems you faced during the development, then we can help on a specic case

  • User Avatar
    0
    roberto.fiocchi created

    Ok, to do it myself I would need links to documentation for these two points.

    • Set the correct url based on the environment in Abp solution

    • Set AccessTokenProvider in Abp solution

    Can you give me the links?

    Thanks

  • User Avatar
    0
    roberto.fiocchi created

    Hi @enisn,
    I can't find the information requested.
    Can you send me links to the documentation?

    Thanks!

  • User Avatar
    0
    roberto.fiocchi created

    It would be nice to have an answer and not wait more days.
    If necessary, I open a new ticket.

  • User Avatar
    0
    enisn created
    Support Team .NET Developer

    Hi

    I sincerely apologize for the delay in my response and appreciate your patience.

    In regard to your request for documentation on the two points you highlighted, please find the details below:

    • Set the correct url based on the environment in Abp solution

    You can refer to the documentation on remote service configurations through the following link: https://abp.io/docs/latest/framework/api-development/dynamic-csharp-clients#configuration

    • Set AccessTokenProvider in Abp solution

    For applications using WebAssembly (WASM), you can resolve the service mentioned in the link below to retrieve your access token:
    https://github.com/abpframework/abp/blob/0b6ae95866a884f054f1684cea0eba62082b3e46/framework/src/Volo.Abp.Http.Client.IdentityModel.WebAssembly/Volo/Abp/Http/Client/IdentityModel/WebAssembly/AccessTokenProviderIdentityModelRemoteServiceHttpClientAuthenticator.cs#L10

  • User Avatar
    0
    roberto.fiocchi created

    ok, thanks for the links.
    Now I will study and try to put them into practice

Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
Do you need assistance from an ABP expert?
Schedule a Meeting
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v9.2.0-preview. Updated on March 20, 2025, 18:00