diff --git a/AsbCloudWebApi/Options/Notifications/NotificationsOptionsSignalR.cs b/AsbCloudWebApi/Options/Notifications/NotificationsOptionsSignalR.cs new file mode 100644 index 00000000..b955bd7a --- /dev/null +++ b/AsbCloudWebApi/Options/Notifications/NotificationsOptionsSignalR.cs @@ -0,0 +1,8 @@ +namespace AsbCloudWebApi.Options.Notifications; + +public class NotificationsOptionsSignalR +{ + public string Method { get; set; } = null!; + + public int IdTransport { get; set; } +} \ No newline at end of file diff --git a/AsbCloudWebApi/SignalR/BackgroundServices/SignalRNotificationSender.cs b/AsbCloudWebApi/SignalR/BackgroundServices/SignalRNotificationSender.cs new file mode 100644 index 00000000..c0d09014 --- /dev/null +++ b/AsbCloudWebApi/SignalR/BackgroundServices/SignalRNotificationSender.cs @@ -0,0 +1,82 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Repositories; +using AsbCloudApp.Services; +using AsbCloudWebApi.Options.Notifications; +using AsbCloudWebApi.SignalR.ConnectionManager; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; + +namespace AsbCloudWebApi.SignalR.BackgroundServices; + +public class SignalRNotificationSender : BackgroundService +{ + private readonly IConnectionManager connectionManager; + private readonly INotificationSendingQueueService notificationSendingQueueService; + private readonly IHubContext notificationHubContext; + private readonly NotificationsOptionsSignalR notificationsOptionsSignalR; + private readonly IServiceProvider serviceProvider; + + public SignalRNotificationSender(IConnectionManager connectionManager, + INotificationSendingQueueService notificationSendingQueueService, + IHubContext notificationHubContext, + IOptions notificationsOptionsSignalR, + IServiceProvider serviceProvider) + { + this.connectionManager = connectionManager; + this.notificationSendingQueueService = notificationSendingQueueService; + this.notificationHubContext = notificationHubContext; + this.notificationsOptionsSignalR = notificationsOptionsSignalR.Value; + this.serviceProvider = serviceProvider; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await Task.Run( async () => + { + await SendAsync(stoppingToken); + }, stoppingToken); + } + + private async Task SendAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + notificationSendingQueueService.Wait(cancellationToken); + + while (!notificationSendingQueueService.IsEmpty) + { + if (notificationSendingQueueService.TryDequeue(out var notification)) + { + string userId = notification.IdUser.ToString(); + + var connectionId = connectionManager.GetConnectionIdByUserId(userId); + + if (!string.IsNullOrWhiteSpace(connectionId)) + { + await notificationHubContext.Clients.Client(connectionId) + .SendAsync(notificationsOptionsSignalR.Method, + notification, + cancellationToken); + + notification.SentDateAtUtc = DateTime.UtcNow; + notification.IsRead = false; + } + + var scope = serviceProvider.CreateScope(); + + var notificationRepository = scope.ServiceProvider.GetService(); + + if (notificationRepository != null) + { + await notificationRepository.UpdateAsync(notification, + cancellationToken); + } + } + } + } + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/SignalR/BaseHub.cs b/AsbCloudWebApi/SignalR/BaseHub.cs new file mode 100644 index 00000000..c351ccc4 --- /dev/null +++ b/AsbCloudWebApi/SignalR/BaseHub.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; + +namespace AsbCloudWebApi.SignalR; + +public abstract class BaseHub : Hub +{ + public virtual Task AddToGroup(string groupName) => + Groups.AddToGroupAsync(Context.ConnectionId, groupName); + + public virtual Task RemoveFromGroup(string groupName) => + Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName); +} + +public abstract class BaseHub : BaseHub + where T : class +{ + +} \ No newline at end of file diff --git a/AsbCloudWebApi/SignalR/ConnectionManager/ConnectionManager.cs b/AsbCloudWebApi/SignalR/ConnectionManager/ConnectionManager.cs new file mode 100644 index 00000000..656c28ff --- /dev/null +++ b/AsbCloudWebApi/SignalR/ConnectionManager/ConnectionManager.cs @@ -0,0 +1,25 @@ +using System.Collections.Concurrent; + +namespace AsbCloudWebApi.SignalR.ConnectionManager; + +public class ConnectionManager : IConnectionManager +{ + private readonly ConcurrentDictionary _connections = new(); + + public void AddConnection(string userId, + string connectionId) + { + _connections.TryAdd(userId, connectionId); + } + + public void RemoveConnection(string userId) + { + _connections.TryRemove(userId, out _); + } + + public string? GetConnectionIdByUserId(string userId) + { + _connections.TryGetValue(userId, out string? connectionId); + return connectionId; + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/SignalR/ConnectionManager/IConnectionManager.cs b/AsbCloudWebApi/SignalR/ConnectionManager/IConnectionManager.cs new file mode 100644 index 00000000..9584d32b --- /dev/null +++ b/AsbCloudWebApi/SignalR/ConnectionManager/IConnectionManager.cs @@ -0,0 +1,11 @@ +namespace AsbCloudWebApi.SignalR.ConnectionManager; + +public interface IConnectionManager +{ + void AddConnection(string userId, + string connectionId); + + void RemoveConnection(string userId); + + string? GetConnectionIdByUserId(string userId); +} \ No newline at end of file diff --git a/AsbCloudWebApi/SignalR/NotificationHub.cs b/AsbCloudWebApi/SignalR/NotificationHub.cs new file mode 100644 index 00000000..322808cc --- /dev/null +++ b/AsbCloudWebApi/SignalR/NotificationHub.cs @@ -0,0 +1,39 @@ +using System.Threading; +using System.Threading.Tasks; +using AsbCloudApp.Services; +using AsbCloudWebApi.Options.Notifications; +using AsbCloudWebApi.SignalR.ConnectionManager; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Options; + +namespace AsbCloudWebApi.SignalR; + +[Authorize] +public class NotificationHub : BaseHub +{ + private readonly IConnectionManager connectionManager; + private readonly INotificationService notificationService; + private readonly NotificationsOptionsSignalR notificationsOptionsSignalR; + + public NotificationHub(IConnectionManager connectionManager, + INotificationService notificationService, + IOptions notificationsOptionsSignalR) + { + this.connectionManager = connectionManager; + this.notificationService = notificationService; + this.notificationsOptionsSignalR = notificationsOptionsSignalR.Value; + } + + public async Task OnConnected(int idUser) + { + string connectionId = Context.ConnectionId; + + connectionManager.AddConnection(idUser.ToString(), connectionId); + + await notificationService.ResendNotificationAsync(idUser, + notificationsOptionsSignalR.IdTransport, + CancellationToken.None); + + await base.OnConnectedAsync(); + } +} \ No newline at end of file diff --git a/AsbCloudWebApi/SignalR/ReportsHub.cs b/AsbCloudWebApi/SignalR/ReportsHub.cs index 364e731d..b0bd95b7 100644 --- a/AsbCloudWebApi/SignalR/ReportsHub.cs +++ b/AsbCloudWebApi/SignalR/ReportsHub.cs @@ -1,6 +1,4 @@ using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.SignalR; -using System.Threading.Tasks; namespace AsbCloudWebApi.SignalR { @@ -8,12 +6,8 @@ namespace AsbCloudWebApi.SignalR // https://docs.microsoft.com/ru-ru/aspnet/core/signalr/introduction?view=aspnetcore-5.0 [Authorize] - public class ReportsHub : Hub + public class ReportsHub : BaseHub { - public Task AddToGroup(string groupName) - => Groups.AddToGroupAsync(Context.ConnectionId, groupName); - - public Task RemoveFromGroup(string groupName) - => Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName); + } } diff --git a/AsbCloudWebApi/SignalR/TelemetryHub.cs b/AsbCloudWebApi/SignalR/TelemetryHub.cs index 100026e4..9b7fd59d 100644 --- a/AsbCloudWebApi/SignalR/TelemetryHub.cs +++ b/AsbCloudWebApi/SignalR/TelemetryHub.cs @@ -1,6 +1,4 @@ using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.SignalR; -using System.Threading.Tasks; namespace AsbCloudWebApi.SignalR { @@ -8,12 +6,8 @@ namespace AsbCloudWebApi.SignalR // https://docs.microsoft.com/ru-ru/aspnet/core/signalr/introduction?view=aspnetcore-5.0 [Authorize] - public class TelemetryHub : Hub + public class TelemetryHub : BaseHub { - public Task AddToGroup(string groupName) - => Groups.AddToGroupAsync(Context.ConnectionId, groupName.ToString()); - - public Task RemoveFromGroup(string groupName) - => Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName); + } } \ No newline at end of file diff --git a/AsbCloudWebApi/Startup.cs b/AsbCloudWebApi/Startup.cs index 6d14e3dc..3e1c56c3 100644 --- a/AsbCloudWebApi/Startup.cs +++ b/AsbCloudWebApi/Startup.cs @@ -1,7 +1,10 @@ using AsbCloudInfrastructure; using AsbCloudWebApi.Converters; using AsbCloudWebApi.Middlewares; +using AsbCloudWebApi.Options.Notifications; using AsbCloudWebApi.SignalR; +using AsbCloudWebApi.SignalR.BackgroundServices; +using AsbCloudWebApi.SignalR.ConnectionManager; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -44,7 +47,10 @@ namespace AsbCloudWebApi services.AddJWTAuthentication(); - services.AddSignalR(); + services.AddSignalR() + .Services.AddSingleton() + .AddHostedService() + .Configure(Configuration.GetSection("NotificationsOptionsSignalR")); services.AddCors(options => { @@ -147,6 +153,7 @@ namespace AsbCloudWebApi app.UseEndpoints(endpoints => { endpoints.MapControllers(); + endpoints.MapHub("/hubs/notifications"); endpoints.MapHub("/hubs/telemetry"); endpoints.MapHub("/hubs/reports"); }); diff --git a/AsbCloudWebApi/appsettings.json b/AsbCloudWebApi/appsettings.json index 683f7929..5014dbf2 100644 --- a/AsbCloudWebApi/appsettings.json +++ b/AsbCloudWebApi/appsettings.json @@ -27,6 +27,10 @@ "supportMail": "support@digitaldrilling.ru" }, "DirectoryNameHelpPageFiles": "helpPages", + "NotificationsOptionsSignalR": { + "Method": "notifications", + "IdTransport": 1 + }, "Urls": "http://0.0.0.0:5000" //;https://0.0.0.0:5001" //, // See https man: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel/endpoints?view=aspnetcore-6.0 //"Kestrel": {