【问题标题】:How to tell when a user leaves the site with Blazor Server Side如何使用 Blazor 服务器端判断用户何时离开站点
【发布时间】:2020-10-22 10:00:35
【问题描述】:

Blazor 服务器端基于 Signalr,因此我假设它知道用户何时离开网站(关闭连接)。是否有任何事件可用于记录此事件?或者其他任何方式!

【问题讨论】:

    标签: blazor blazor-server-side


    【解决方案1】:

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

    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);
            }
                   
        }
    

    测试

    Index.razor

    @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());
        }
    }
    

    Startup.cs

    public void ConfigureServices(IServiceCollection services)
            {
                services.AddRazorPages();
                services.AddServerSideBlazor();
    
                services.AddSingleton<CircuitHandler>(new CircuitHandlerService());
            }
    

    希望这会有所帮助...

    【讨论】:

    • 谢谢!这对我来说是一个很好的开始。您知道我现在如何从电路中引用电路 ID 吗? (所以当用户登录时,我可以将他们的用户名与电路 ID 相关联)
    • 抱歉,我忘记了您的评论...我猜您的意思是如何从电路处理程序中引用电路 ID,对吧?一般来说,您可以使用用户名作为 ConcurrentDictionary Circuits 中的键,而不是 circuit.Id,从而将 Circuit 对象与用户名相关联。您可以将 AuthenticationStateProvider 注入 CircuitHandler,并提取用户名,但问题是 AuthenticationStateProvider 是作用域,而 CircuitHandler 是 Singleton。所以你不能同时使用两者。您可以将 CircuitHandler 服务设为作用域,
    • 但它变得无用,因为它只引用当前应用程序。您可以尝试通过 HttpContext 从启动类中提取用户名,然后将其传递给 CircuitHandler 服务的构造函数。我认为这是可行的。您还应该考虑用户未通过身份验证的情况,因此您要传递给 CircuitHandler 的参数将为空。请注意,如果用户打开应用程序的新实例,他可以与多个电路相关联。
    • 我建议您搜索与电路对象相关的材料。我对这些高级的东西不太熟悉,文档也很少涵盖它。祝你好运
    【解决方案2】:

    我会做并且已经做了以下事情。

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

    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。

    【讨论】:

    • 我根据这篇文章实现了一个解决方案。它看起来运作良好。我的主要收获是使 CircuitHandlerService 作用域而不是单例。这允许用户数据和电路 ID 之间的关联。谢谢杰斯珀!
    • 谢谢,这非常适合作为跟踪用户活动的一种方式
    • 我正在做这个实现,但是当我在 MainLayout 中注入 'CircuitHandlerService' 时,CircuitId 为空
    【解决方案3】:

    正如 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);
            
        }
    }
    

    【讨论】:

    • IUsersStateContainer 来自哪里?
    【解决方案4】:

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

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

    当用户断开连接时触发。 Hub 的 Context 属性包含有关用户的信息(至少是连接 ID)。

    【讨论】:

    • 很遗憾,这无法访问 blazor 集线器,因此我无法使用它来跟踪 blazor 电路的状态
    猜你喜欢
    • 2014-08-22
    • 1970-01-01
    • 2011-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-28
    • 2017-05-05
    相关资源
    最近更新 更多