Skip to content

Commit

Permalink
Merge pull request #794 from neozhu/refactor_usersstate
Browse files Browse the repository at this point in the history
UsersStateContainer
  • Loading branch information
neozhu authored Nov 1, 2024
2 parents 3d6613f + 1fd0c29 commit 907e45d
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;

namespace CleanArchitecture.Blazor.Application.Common.Interfaces.Identity;

public interface IUsersStateContainer
{
ConcurrentDictionary<string, string> UsersByConnectionId { get; }
event Action? OnChange;
void AddOrUpdate(string connectionId, string? name);
void AddOrUpdate(string connectionId, string? userId);
void Remove(string connectionId);
void Clear(string userId);
}
42 changes: 39 additions & 3 deletions src/Infrastructure/Services/Identity/UsersStateContainer.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,63 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;

namespace CleanArchitecture.Blazor.Infrastructure.Services.Identity;


/// <summary>
/// Manages the state of users by their connection IDs.
/// </summary>
public class UsersStateContainer : IUsersStateContainer
{
/// <summary>
/// Gets the dictionary that maps connection IDs to user names.
/// </summary>
public ConcurrentDictionary<string, string> UsersByConnectionId { get; } = new();

/// <summary>
/// Event triggered when the state changes.
/// </summary>
public event Action? OnChange;

/// <summary>
/// Adds or updates a user in the state container.
/// </summary>
/// <param name="connectionId">The connection ID of the user.</param>
/// <param name="name">The name of the user.</param>
public void AddOrUpdate(string connectionId, string? name)
{
UsersByConnectionId.AddOrUpdate(connectionId, name ?? string.Empty, (key, oldValue) => name ?? string.Empty);
NotifyStateChanged();
}

/// <summary>
/// Removes a user from the state container by their connection ID.
/// </summary>
/// <param name="connectionId">The connection ID of the user to remove.</param>
public void Remove(string connectionId)
{
UsersByConnectionId.TryRemove(connectionId, out var _);
UsersByConnectionId.TryRemove(connectionId, out _);
NotifyStateChanged();
}

/// <summary>
/// Clears all users with the specified name from the state container.
/// </summary>
/// <param name="userName">The name of the user to clear.</param>
public void Clear(string userName)
{
var keysToRemove = UsersByConnectionId.Where(kvp => kvp.Value == userName).Select(kvp => kvp.Key).ToList();
foreach (var key in keysToRemove)
{
UsersByConnectionId.TryRemove(key, out _);
}
NotifyStateChanged();
}

/// <summary>
/// Notifies subscribers that the state has changed.
/// </summary>
private void NotifyStateChanged()
{
OnChange?.Invoke();
}
}
}
4 changes: 1 addition & 3 deletions src/Server.UI/Components/Identity/UserLoginState.razor
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@


[Inject] private HubClient Client { get; set; } = default!;
[Inject] private IUsersStateContainer UsersStateContainer { get; set; } = default!;

protected override async Task OnInitializedAsync()
{
Expand All @@ -32,7 +31,6 @@
InvokeAsync(() =>
{
Snackbar.Add(string.Format(L["{0} has logged in."], args.UserName), Severity.Info);
UsersStateContainer.AddOrUpdate(args.ConnectionId, args.UserName);
});
}

Expand All @@ -41,7 +39,7 @@
InvokeAsync(() =>
{
Snackbar.Add(string.Format(L["{0} has logged out."], args.UserName));
UsersStateContainer.Remove(args.ConnectionId);

});
}

Expand Down
17 changes: 13 additions & 4 deletions src/Server.UI/Middlewares/UserSessionCircuitHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CleanArchitecture.Blazor.Server.UI.Services.Fusion;
using CleanArchitecture.Blazor.Application.Common.Interfaces.Identity;
using CleanArchitecture.Blazor.Server.UI.Services.Fusion;
using Microsoft.AspNetCore.Components.Server.Circuits;

namespace CleanArchitecture.Blazor.Server.UI.Middlewares;
Expand All @@ -10,6 +11,7 @@ public class UserSessionCircuitHandler : CircuitHandler
{
private readonly IUserSessionTracker _userSessionTracker;
private readonly IOnlineUserTracker _onlineUserTracker;
private readonly IUsersStateContainer _usersStateContainer;
private readonly IHttpContextAccessor _httpContextAccessor;

/// <summary>
Expand All @@ -18,10 +20,11 @@ public class UserSessionCircuitHandler : CircuitHandler
/// <param name="userSessionTracker">The user session tracker service.</param>
/// <param name="onlineUserTracker">The online user tracker service.</param>
/// <param name="httpContextAccessor">The HTTP context accessor.</param>
public UserSessionCircuitHandler(IUserSessionTracker userSessionTracker, IOnlineUserTracker onlineUserTracker, IHttpContextAccessor httpContextAccessor)
public UserSessionCircuitHandler(IUserSessionTracker userSessionTracker, IOnlineUserTracker onlineUserTracker, IUsersStateContainer usersStateContainer, IHttpContextAccessor httpContextAccessor)
{
_userSessionTracker = userSessionTracker;
_onlineUserTracker = onlineUserTracker;
_usersStateContainer = usersStateContainer;
_httpContextAccessor = httpContextAccessor;
}

Expand All @@ -33,6 +36,11 @@ public UserSessionCircuitHandler(IUserSessionTracker userSessionTracker, IOnline
/// <returns>A task that represents the asynchronous operation.</returns>
public override async Task OnConnectionUpAsync(Circuit circuit, CancellationToken cancellationToken)
{
var userId = (_httpContextAccessor.HttpContext?.User.Identity?.IsAuthenticated??false)?_httpContextAccessor.HttpContext?.User.GetUserId():string.Empty;
if (!string.IsNullOrEmpty(userId))
{
_usersStateContainer.AddOrUpdate(circuit.Id, userId);
}
await base.OnConnectionUpAsync(circuit, cancellationToken);
}

Expand All @@ -44,13 +52,14 @@ public override async Task OnConnectionUpAsync(Circuit circuit, CancellationToke
/// <returns>A task that represents the asynchronous operation.</returns>
public override async Task OnConnectionDownAsync(Circuit circuit, CancellationToken cancellationToken)
{
var userId = _httpContextAccessor.HttpContext?.User.GetUserId();

var userId = (_httpContextAccessor.HttpContext?.User.Identity?.IsAuthenticated ?? false) ? _httpContextAccessor.HttpContext?.User.GetUserId() : string.Empty;
if (!string.IsNullOrEmpty(userId))
{
await _userSessionTracker.RemoveAllSessions(userId, cancellationToken);
await _onlineUserTracker.Clear(userId, cancellationToken);
_usersStateContainer.Remove(circuit.Id);
}


await base.OnConnectionDownAsync(circuit, cancellationToken);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Server.UI/Pages/Identity/Users/Components/UserCard.razor
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ else

private bool IsOnline()
{
var username = Item?.DisplayName ?? Item?.UserName;
return UsersStateContainer.UsersByConnectionId.Any(x => x.Value.Equals(username, StringComparison.OrdinalIgnoreCase));
var userId = Item?.Id;
return UsersStateContainer.UsersByConnectionId.Any(x => x.Value.Equals(userId, StringComparison.OrdinalIgnoreCase));
}

private void SendVerify()
Expand Down

0 comments on commit 907e45d

Please sign in to comment.