如何判断用户何时离开Blazor服务器端的站点

如何判断用户何时离开Blazor服务器端的站点,blazor,blazor-server-side,Blazor,Blazor Server Side,Blazor服务器端是基于Signal构建的,所以我假设它知道用户何时离开网站(关闭连接)。是否有任何引发的事件可用于记录此事件?或者任何其他方式真的 我认为这项服务可以帮助您 public class CircuitHandlerService : CircuitHandler { public ConcurrentDictionary<string, Circuit> Circuits { get; set; } public event

Blazor服务器端是基于Signal构建的,所以我假设它知道用户何时离开网站(关闭连接)。是否有任何引发的事件可用于记录此事件?或者任何其他方式真的

我认为这项服务可以帮助您

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);
        }
               
    }
公共类CircuitHandler服务:CircuitHandler
{
公共ConcurrentDictionary电路{get;set;}
公共事件处理程序发生故障;
受保护的虚拟void OnCircuitsChanged()
=>CircuitsChanged?.Invoke(这个,EventArgs.Empty);
公共电路HandlerService()
{
电路=新的ConcurrentDictionary();
}
公用覆盖任务OnCircuitOpenedAsync(电路,取消令牌取消令牌)
{
电路[circuit.Id]=电路;
OnCircuitsChanged();
返回base.OnCircuitOpenedAsync(电路,取消令牌);
}
CircuitClosedAsync上的公共覆盖任务(电路,取消令牌取消令牌)
{
Console.WriteLine(“OnCircuitClosedAsync”);
拆除电路;
Circuits.TryRemove(circuit.Id,out-circuit已移除);
OnCircuitsChanged();
返回base.OnCircuitClosedAsync(电路,取消令牌);
}
公共覆盖任务OnConnectionDownAsync(电路电路,CancellationToken CancellationToken)
{
WriteLine(“OnConnectionDownAsync”);
返回base.OnConnectionDownAsync(电路,取消令牌);
}
公共覆盖任务OnConnectionUpAsync(电路电路,CancellationToken CancellationToken)
{
返回base.OnConnectionUpAsync(电路,取消令牌);
}
}
测试

剃刀索引
@page/“
@使用Microsoft.AspNetCore.Components.Server.Circuits
@使用BlazorCircuitHandler.Services
@注入电路处理程序电路处理程序
@实现IDisposable
你好,世界!
欢迎使用您的新应用程序。

电路数:@((circuitHandler as.Services.CircuitHandlerService.Circuits.Count)
    @foreach(var电路输入(作为BlazorCircuitHandler.Services.CircuitHandlerService.Circuits的电路处理器) {
  • @电路,钥匙
  • }

@代码{ 受保护的覆盖无效OnInitialized() { (circuitHandler作为CircuitHandlerService)。CircuitsChanged+=HandleCircuitsChanged; } 公共空间处置() { (circuitHandler作为CircuitHandlerService)。CircuitsChanged-=HandleCircuitsChanged; } public void HandleCircuitsChanged(对象发送方、事件args args) { //通知UI状态已更改 InvokeAsync(()=>StateHasChanged()); } }
Startup.cs
public void配置服务(IServiceCollection服务)
{
services.AddRazorPages();
AddServerSideBlazor();
AddSingleton(新的CircuitHandlerService());
}

希望这有帮助…

在您的应用程序中,可能有一个类派生自Microsoft.AspNetCore.signal.Hub。如果没有,您可以创建一个。那么在这节课上

public class MyHub : Hub
{
    public override async Task OnDisconnectedAsync(Exception exception)
}

当用户断开连接时激发。集线器的Context属性包含有关用户的信息(至少是连接id)。

我会做并且已经做了以下操作

使用以下签名创建一个接口和该接口的实现,以适合您的场景。这将是您的中心服务,跟踪您的用户联机状态

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>()));
services.AddScoped((sp)=>newCircuitHandlerService(sp.GetRequiredService());

现在,将您的CircuitHandler和IUserOnLineservice插入到您的登录逻辑所在的位置,然后在用户身份验证后,使用CircuitHandler上的CircuitId属性,该属性将保存当前连接的Id,并在IUserOnLineservice接口上调用Connect()。断开连接是在CircuitHandler中处理的,因为我们这里只需要(并且拥有)电路Id。

因为Enet已经评论过,如果您想在中注入AuthenticationStateProvider,TrackingCircuitHandler还需要作为AuthenticationStateProvider确定范围

如果要跟踪应用程序中的用户名,可以将作为单例使用,并将其注入TrackingCircuitHandler

如果您只想在用户离开网站时登录,则可以跳过IUsersStateContainer,只需插入记录器

首先创建内存中的“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();
    }
将此作为作用域服务添加到startup.cs:

services.AddScoped<CircuitHandler, TrackingCircuitHandler>();
services.addScope();
要在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);
        
    }
}
@page”/UserStatePage
@注入IUsersStateContainer UsersStateContainer
@实现IDisposable
连接用户
    @foreach(UsersStateContainer.UsersByConnectionId中的var用户) {
  • @user.Key-@user.Value
  • }
@代码{ 受保护的重写异步任务OnInitializedAsync() { UsersStateContainer.OnChange+=OnMyChangeHandler; } 公共空间处置() { UsersStateContainer.OnChange-=OnMyChangeHandler; } 私有异步void OnMyChangeHandler() { //在ui线程上调用 等待调用同步(StateHasChanged); } }
不幸的是,这无法访问blazor集线器,因此我无法使用它跟踪blazor电路的状态!这对我来说是一个很好的开始。你知道吗
services.AddSingleton<IUsersStateContainer, UsersStateContainer>();
 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;
        }

       
    }
services.AddScoped<CircuitHandler, TrackingCircuitHandler>();
@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);
        
    }
}