Hi,
The application and also your code behaviour can be different in the debug mode and they won't be optimized for the best performance.
You can try disabling BackgroundWorkers in debug mode if you don't need at your work and make the starting process faster
#if DEBUG
Configure<AbpBackgroundWorkerOptions>(options =>{
options.IsEnabled = false;
});
#endif
Hi,
Do you use generating client proxies command without contracts ?
https://abp.io/docs/latest/framework/api-development/static-csharp-clients#without-contracts
I'll try to provide a really qucik example:
[Authorize]
public class NotificationHub : Hub
{
public async Task SendNotification(string message)
{
await Clients.All.SendAsync("ReceiveNotification", message);
}
}
Make sure you have
Volo.Abp.AspNetCore.SignalRpackage 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<NotificationHub>("/signalr-hubs/notification");
});
```
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" />
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.
Hi,
Can you double check if abp install-libs command is executed in the pipeline.
Also please check ABI CLI version in the machine that pipeline runs on it.
Somehow it might be updated or removed, you can use the exact same cli version with your project to restore packages properly:
dotnet tool update -g volo.abp.cli --version 8.2.1
Hi,
It's hard to determine the real problem since they're third-party libraries. But still all the OpenID Connect providers should work exactly same.There might be some configuration problems. Let's find out with investigating more information in your application.
Can you check for any extra inrformation about what happens while trying to login in AuthServer project logs?
Hi,
Is there any logs on the browser console? Can you share ?
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:
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.
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.
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.
Hi,
Thanks for your report, We'll check this case as soon as possible
Hi,
ExtraProperties are stored as JSON in the database but whenever you read data, they'll be deserialized into a Dictionary in the C#.
But it seems you're using multi-level objects since you have only 1 key in your ExtraProperties. Since it's a Dictionary<string, object?> typed property, your value may be a JsonObject or Jobject depending on your configuration and serializer library.
https://github.com/abpframework/abp/blob/fc9147ead4e226b1a4d56cd22b4fcd6357ecb15d/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs#L61
If you define specific types to your ExtraProperties or map them into separate columns, you can always use Object Extensions feature to congiure them: https://abp.io/docs/latest/framework/fundamentals/object-extensions