【发布时间】:2020-10-22 10:00:35
【问题描述】:
Blazor 服务器端基于 Signalr,因此我假设它知道用户何时离开网站(关闭连接)。是否有任何事件可用于记录此事件?或者其他任何方式!
【问题讨论】:
Blazor 服务器端基于 Signalr,因此我假设它知道用户何时离开网站(关闭连接)。是否有任何事件可用于记录此事件?或者其他任何方式!
【问题讨论】:
我认为这项服务可以帮助您...
public class CircuitHandlerService : CircuitHandler
{
public ConcurrentDictionary<string, Circuit> Circuits { get; set; }
public event EventHandler CircuitsChanged;
protected virtual void OnCircuitsChanged()
=> CircuitsChanged?.Invoke(this, EventArgs.Empty);
public CircuitHandlerService()
{
Circuits = new ConcurrentDictionary<string, Circuit>();
}
public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
{
Circuits[circuit.Id] = circuit;
OnCircuitsChanged();
return base.OnCircuitOpenedAsync(circuit, cancellationToken);
}
public override Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken)
{
Console.WriteLine("OnCircuitClosedAsync");
Circuit circuitRemoved;
Circuits.TryRemove(circuit.Id, out circuitRemoved);
OnCircuitsChanged();
return base.OnCircuitClosedAsync(circuit, cancellationToken);
}
public override Task OnConnectionDownAsync(Circuit circuit, CancellationToken cancellationToken)
{
Console.WriteLine("OnConnectionDownAsync");
return base.OnConnectionDownAsync(circuit, cancellationToken);
}
public override Task OnConnectionUpAsync(Circuit circuit, CancellationToken cancellationToken)
{
return base.OnConnectionUpAsync(circuit, cancellationToken);
}
}
测试
@page "/"
@using Microsoft.AspNetCore.Components.Server.Circuits
@using BlazorCircuitHandler.Services
@inject CircuitHandler circuitHandler
@implements IDisposable
<h1>Hello, world!</h1>
Welcome to your new app.
<p>
Number of Circuits: @((circuitHandler as <BlazorCircuitHandler is a name space in my app>.Services.CircuitHandlerService).Circuits.Count)
<ul>
@foreach (var circuit in (circuitHandler as BlazorCircuitHandler.Services.CircuitHandlerService).Circuits)
{
<li>@circuit.Key</li>
}
</ul>
</p>
@code {
protected override void OnInitialized()
{
(circuitHandler as CircuitHandlerService).CircuitsChanged += HandleCircuitsChanged;
}
public void Dispose()
{
(circuitHandler as CircuitHandlerService).CircuitsChanged -= HandleCircuitsChanged;
}
public void HandleCircuitsChanged(object sender, EventArgs args)
{
// notify the UI that the state has changed
InvokeAsync(() => StateHasChanged());
}
}
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<CircuitHandler>(new CircuitHandlerService());
}
希望这会有所帮助...
【讨论】:
我会做并且已经做了以下事情。
使用以下签名创建一个适合您的场景的接口和该接口的实现。这将是您跟踪用户在线状态的中心服务。
public interface IUserOnlineService
{
void Connect(string circuitId, User user);
void DisConnect(string circuitId);
}
在启动时将接口的实现作为单例注入。
然后创建一个派生自 CircuitHandler 的自定义类。它应该使用您之前创建的 IUserOnlineService 的实例。
public class CircuitHandlerService : CircuitHandler
{
public string CircuitId { get; set; }
IUserOnlineService useronlineservice;
public CircuitHandlerService(IUserOnlineService useronlineservice)
{
this.useronlineservice = useronlineservice;
}
public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken cancellationToken)
{
CircuitId = circuit.Id;
return base.OnCircuitOpenedAsync(circuit, cancellationToken);
}
public override Task OnCircuitClosedAsync(Circuit circuit, CancellationToken cancellationToken)
{
useronlineservice.DisConnect(circuit.Id);
return base.OnCircuitClosedAsync(circuit, cancellationToken);
}
}
在您的启动中将此服务添加为范围服务。这样会有一个实例pr连接。
services.AddScoped<CircuitHandler>((sp) => new CircuitHandlerService(sp.GetRequiredService<IUserOnlineService>()));
现在,无论您拥有登录逻辑的任何位置,都将您的 CircuitHandler 和 IUserOnLineservice 注入,然后在用户身份验证之后,使用 Circuithandler 上的 CircuitId 属性,该属性将保存您当前连接的 Id,并在 IUserOnlineService 接口上调用 Connect()。断开连接在 CircuitHandler 中处理,因为我们在这里只需要(并且拥有)电路 ID。
【讨论】:
正如 Enet 已经评论的那样,如果您想在 TrackingCircuitHandler 中注入 AuthenticationStateProvider,则 TrackingCircuitHandler 还需要限定为 AuthenticationStateProvider。
如果您想跟踪应用程序中的用户名,您可以使用 In-memory state container service 作为单例并将其注入 TrackingCircuitHandler。
如果您只想在用户离开网站时进行记录,您可以跳过 IUsersStateContainer 并直接注入 Logger。
首先在内存中创建一个“UsersStateContainer”:
public class UsersStateContainer : IUsersStateContainer
{
public ConcurrentDictionary<string, string> UsersByConnectionId { get; set; } =
new ConcurrentDictionary<string, string>();
public event Action OnChange;
public void Update(string connectionId, string name)
{
UsersByConnectionId.AddOrUpdate(connectionId, name, (key, oldValue) => name );
NotifyStateChanged();
}
public void Remove(string connectionId)
{
UsersByConnectionId.TryRemove(connectionId, out var _);
NotifyStateChanged();
}
private void NotifyStateChanged() => OnChange?.Invoke();
}
在您的启动中将此服务添加为范围服务:
services.AddSingleton<IUsersStateContainer, UsersStateContainer>();
TrackingCircuitHandler 使用 IUserStateContainer:
public class TrackingCircuitHandler: CircuitHandler
{
private IUsersStateContainer _usersStateContainer;
private AuthenticationStateProvider _authenticationStateProvider;
public TrackingCircuitHandler(IUsersStateContainer usersStateContainer, AuthenticationStateProvider authenticationStateProvider)
{
_usersStateContainer = usersStateContainer;
_authenticationStateProvider = authenticationStateProvider;
}
public override async Task OnConnectionUpAsync(Circuit circuit,
CancellationToken cancellationToken)
{
var state = await _authenticationStateProvider.GetAuthenticationStateAsync();
_usersStateContainer.Update(circuit.Id, state.User.Identity.Name);
return ;
}
public override Task OnConnectionDownAsync(Circuit circuit,
CancellationToken cancellationToken)
{
_usersStateContainer.Remove(circuit.Id);
return Task.CompletedTask;
}
}
将此作为作用域服务添加到 startup.cs:
services.AddScoped<CircuitHandler, TrackingCircuitHandler>();
在 Blazor 页面上显示活动用户:
@page "/UserStatePage"
@inject IUsersStateContainer UsersStateContainer
@implements IDisposable
<h2>Connected users</h2>
<ul>
@foreach (var user in UsersStateContainer.UsersByConnectionId)
{
<li>@user.Key - @user.Value</li>
}
</ul>
@code {
protected override async Task OnInitializedAsync()
{
UsersStateContainer.OnChange += OnMyChangeHandler;
}
public void Dispose()
{
UsersStateContainer.OnChange -= OnMyChangeHandler;
}
private async void OnMyChangeHandler()
{
// invoke on ui thread
await InvokeAsync(StateHasChanged);
}
}
【讨论】:
在您的应用中,可能有一个派生自 Microsoft.AspNetCore.SignalR.Hub 的类。如果没有,您可以创建一个。那么在这堂课中
public class MyHub : Hub
{
public override async Task OnDisconnectedAsync(Exception exception)
}
当用户断开连接时触发。 Hub 的 Context 属性包含有关用户的信息(至少是连接 ID)。
【讨论】: