Asp.net core 关闭/断开ASP.NET Core Signal客户端连接的正确方法是什么?

Asp.net core 关闭/断开ASP.NET Core Signal客户端连接的正确方法是什么?,asp.net-core,signalr,blazor-server-side,signalr.client,asp.net-core-signalr,Asp.net Core,Signalr,Blazor Server Side,Signalr.client,Asp.net Core Signalr,我是一个新用户,正在努力从ASP.NET Core Blazor服务器页面优雅地关闭辅助信号器客户端 我正在Blazor服务器页面的第一次呈现上设置一个辅助信号器客户端连接。当页面通过浏览器选项卡关闭时,我正在尝试关闭此辅助信号器客户端连接 在编写时,通过浏览器选项卡关闭页面时,似乎不会触发DisposeAsync。但是,会触发Dispose方法。此外,在Safari 13.0.5中,浏览器选项卡关闭时不会触发Dispose方法?Opera、Firefox和Chrome都在关闭浏览器选项卡时触发

我是一个新用户,正在努力从ASP.NET Core Blazor服务器页面优雅地关闭辅助信号器客户端

我正在Blazor服务器页面的第一次呈现上设置一个辅助信号器客户端连接。当页面通过浏览器选项卡关闭时,我正在尝试关闭此辅助信号器客户端连接

在编写时,通过浏览器选项卡关闭页面时,似乎不会触发
DisposeAsync
。但是,会触发
Dispose
方法。此外,在Safari 13.0.5中,浏览器选项卡关闭时不会触发
Dispose
方法?Opera、Firefox和Chrome都在关闭浏览器选项卡时触发了
Dispose
。通过macOS Catalina v10.15.7将Safari更新为v14.0(15610.1.28.915610)修复了此问题

目前,我正在从
Dispose
呼叫
DisposeAsync
以关闭信号机连接。我正在使用以下代码关闭客户端连接:

...
Logger.LogInformation("Closing secondary signalR connection...");
await hubConnection.StopAsync();
Logger.LogInformation("Closed secondary signalR connection");
...
StopAsync
方法出现阻塞,即“关闭的辅助信号机连接”没有输出消息。尽管如此,我的服务器集线器的
OnDisconnectedAsync
处理程序显示连接已断开。这与本文中描述的类似

如何正确处理ASP.NET Core 3.1中的信号器连接

完整代码清单如下所示:

处理信号机连接

 #region Dispose
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        /// <summary>
        /// Clear secondary signalR Closed event handler and stop the
        /// secondary signalR connection
        /// </summary>
        /// <remarks>
        /// ASP.NET Core Release Candidate 5 calls DisposeAsync when 
        /// navigating away from a Blazor Server page. Until the 
        /// release is stable DisposeAsync will have to be triggered from
        /// Dispose. Sadly, this means having to use GetAwaiter().GetResult()
        /// in Dispose().
        /// However, providing DisposeAsync() now makes the migration easier
        /// https://github.com/dotnet/aspnetcore/issues/26737
        /// https://github.com/dotnet/aspnetcore/issues/9960
        /// https://github.com/dotnet/aspnetcore/milestone/57?closed=1
        /// </remarks>
        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
                return;

            if (disposing)
            {
                Logger.LogInformation("Index.razor page is disposing...");

                try
                {
                    if (hubConnection != null)
                    {
                        Logger.LogInformation("Removing signalR client event handlers...");
                        hubConnection.Closed -= CloseHandler;
                    }

                    // Until ASP.NET Core 5 is released in November
                    // trigger DisposeAsync(). See docstring and DiposeAsync() below.
                    // not ideal, but having to use GetAwaiter().GetResult() until
                    // forthcoming release of ASP.NET Core 5 for the introduction
                    // of triggering DisposeAsync on pages that implement IAsyncDisposable
                    DisposeAsync().GetAwaiter().GetResult();
                }
                catch (Exception exception)
                {
                    Logger.LogError($"Exception encountered while disposing Index.razor page :: {exception.Message}");
                }
            }

            disposed = true;
        }


        /// <summary>
        /// Dispose the secondary backend signalR connection
        /// </summary>
        /// <remarks>
        /// ASP.NET Core Release Candidate 5 adds DisposeAsync when 
        /// navigating away from a Blazor Server page. Until the 
        /// release is stable DisposeAsync will have to be triggered from
        /// Dispose. Sadly, this means having to use GetAwaiter().GetResult()
        /// in Dispose().
        /// However, providing DisposeAsync() now makes the migration easier
        /// https://github.com/dotnet/aspnetcore/issues/26737
        /// https://github.com/dotnet/aspnetcore/issues/9960
        /// https://github.com/dotnet/aspnetcore/milestone/57?closed=1
        /// </remarks>
        public async virtual ValueTask DisposeAsync()
        {
            try
            {
                if (hubConnection != null)
                {
                    Logger.LogInformation("Closing secondary signalR connection...");
                    await hubConnection.StopAsync();
                    Logger.LogInformation("Closed secondary signalR connection");
                }
                // Dispose(); When migrated to ASP.NET Core 5 let DisposeAsync trigger Dispose
            }
            catch (Exception exception)
            {
                Logger.LogInformation($"Exception encountered wwhile stopping secondary signalR connection :: {exception.Message}");
            }
        }
        #endregion
#区域处置
公共空间处置()
{
处置(真实);
总干事(本);
}
/// 
///清除辅助信号器关闭事件处理程序并停止
///辅助信号机连接
/// 
/// 
///ASP.NET核心版本候选5在以下情况下调用DisposeAsync
///从Blazor服务器页面导航离开。直到
///释放稳定DisposeAsync必须从
///处置。不幸的是,这意味着必须使用GetAwaiter().GetResult()
///在Dispose()中。
///但是,现在提供DisposeAsync()可以简化迁移
/// https://github.com/dotnet/aspnetcore/issues/26737
/// https://github.com/dotnet/aspnetcore/issues/9960
/// https://github.com/dotnet/aspnetcore/milestone/57?closed=1
/// 
受保护的虚拟void Dispose(bool disposing)
{
如果(已处置)
返回;
如果(处置)
{
Logger.LogInformation(“Index.razor页面正在处理…”);
尝试
{
if(hubConnection!=null)
{
Logger.LogInformation(“删除信号客户端事件处理程序…”);
hubConnection.Closed-=CloseHandler;
}
//直到11月发布ASP.NET Core 5
//触发器DisposeAsync()。请参阅下面的docstring和DiposeAsync()。
//不理想,但必须使用GetAwaiter().GetResult()直到
//即将发布的ASP.NET Core 5介绍版
//在实现IAsyncDisposable的页面上触发DisposeAsync的
DisposeAsync().GetAwaiter().GetResult();
}
捕获(异常)
{
Logger.LogError($“处理Index.razor页面时遇到异常:{Exception.Message}”);
}
}
这是真的;
}
/// 
///处理辅助后端信号器连接
/// 
/// 
///ASP.NET核心版本候选5在以下情况下添加DisposeAsync
///从Blazor服务器页面导航离开。直到
///释放稳定DisposeAsync必须从
///处置。不幸的是,这意味着必须使用GetAwaiter().GetResult()
///在Dispose()中。
///但是,现在提供DisposeAsync()可以简化迁移
/// https://github.com/dotnet/aspnetcore/issues/26737
/// https://github.com/dotnet/aspnetcore/issues/9960
/// https://github.com/dotnet/aspnetcore/milestone/57?closed=1
/// 
公共异步虚拟值任务DisposeAsync()
{
尝试
{
if(hubConnection!=null)
{
Logger.登录信息(“关闭辅助信号机连接…”);
等待hubConnection.StopAsync();
日志信息(“关闭的辅助信号机连接”);
}
//Dispose();迁移到ASP.NET Core 5时,让DisposeAsync触发Dispose
}
捕获(异常)
{
Logger.LogInformation($“在停止辅助信号器连接时遇到异常::{Exception.Message}”);
}
}
#端区
Blazor服务器页面的完整代码

using System;
using System.Threading.Tasks;

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

using WebApp.Data;
using WebApp.Data.Serializers.Converters;
using WebApp.Data.Serializers.Converters.Visitors;
using WebApp.Repository.Contracts;



namespace WebApp.Pages
{
    public partial class Index : IAsyncDisposable, IDisposable
    {
        private HubConnection hubConnection;
        public bool IsConnected => hubConnection.State == HubConnectionState.Connected;
        private bool disposed = false;


        [Inject]
        public NavigationManager NavigationManager { get; set; }
        [Inject]
        public IMotionDetectionRepository Repository { get; set; }
        [Inject]
        public ILogger<MotionDetectionConverter> LoggerMotionDetection { get; set; }
        [Inject]
        public ILogger<MotionInfoConverter> LoggerMotionInfo { get; set; }
        [Inject]
        public ILogger<JsonVisitor> LoggerJsonVisitor { get; set; }
        [Inject]
        public ILogger<Index> Logger { get; set; }


        #region Dispose
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }


        /// <summary>
        /// Clear secondary signalR Closed event handler and stop the
        /// secondary signalR connection
        /// </summary>
        /// <remarks>
        /// ASP.NET Core Release Candidate 5 calls DisposeAsync when 
        /// navigating away from a Blazor Server page. Until the 
        /// release is stable DisposeAsync will have to be triggered from
        /// Dispose. Sadly, this means having to use GetAwaiter().GetResult()
        /// in Dispose().
        /// However, providing DisposeAsync() now makes the migration easier
        /// https://github.com/dotnet/aspnetcore/issues/26737
        /// https://github.com/dotnet/aspnetcore/issues/9960
        /// https://github.com/dotnet/aspnetcore/milestone/57?closed=1
        /// </remarks>
        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
                return;

            if (disposing)
            {
                Logger.LogInformation("Index.razor page is disposing...");

                try
                {
                    if (hubConnection != null)
                    {
                        Logger.LogInformation("Removing signalR client event handlers...");
                        hubConnection.Closed -= CloseHandler;
                    }

                    // Until ASP.NET Core 5 is released in November
                    // trigger DisposeAsync(). See docstring and DiposeAsync() below.
                    // not ideal, but having to use GetAwaiter().GetResult() until
                    // forthcoming release of ASP.NET Core 5 for the introduction
                    // of triggering DisposeAsync on pages that implement IAsyncDisposable
                    DisposeAsync().GetAwaiter().GetResult();
                }
                catch (Exception exception)
                {
                    Logger.LogError($"Exception encountered while disposing Index.razor page :: {exception.Message}");
                }
            }

            disposed = true;
        }


        /// <summary>
        /// Dispose the secondary backend signalR connection
        /// </summary>
        /// <remarks>
        /// ASP.NET Core Release Candidate 5 adds DisposeAsync when 
        /// navigating away from a Blazor Server page. Until the 
        /// release is stable DisposeAsync will have to be triggered from
        /// Dispose. Sadly, this means having to use GetAwaiter().GetResult()
        /// in Dispose().
        /// However, providing DisposeAsync() now makes the migration easier
        /// https://github.com/dotnet/aspnetcore/issues/26737
        /// https://github.com/dotnet/aspnetcore/issues/9960
        /// https://github.com/dotnet/aspnetcore/milestone/57?closed=1
        /// </remarks>
        public async virtual ValueTask DisposeAsync()
        {
            try
            {
                if (hubConnection != null)
                {
                    Logger.LogInformation("Closing secondary signalR connection...");
                    await hubConnection.StopAsync();
                    Logger.LogInformation("Closed secondary signalR connection");
                }
                // Dispose(); When migrated to ASP.NET Core 5 let DisposeAsync trigger Dispose
            }
            catch (Exception exception)
            {
                Logger.LogInformation($"Exception encountered wwhile stopping secondary signalR connection :: {exception.Message}");
            }
        }
        #endregion


        #region ComponentBase

        /// <summary>
        /// Connect to the secondary signalR hub after rendering.
        /// Perform on the first render. 
        /// </summary>
        /// <remarks>
        /// This could have been performed in OnInitializedAsync but
        /// that method gets executed twice when server prerendering is used.
        /// </remarks>
        protected override async Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender)
            {
                var hubUrl = NavigationManager.BaseUri.TrimEnd('/') + "/motionhub";

                try
                {
                    Logger.LogInformation("Index.razor page is performing initial render, connecting to secondary signalR hub");

                    hubConnection = new HubConnectionBuilder()
                        .WithUrl(hubUrl)
                        .ConfigureLogging(logging =>
                        {
                            logging.AddConsole();
                            logging.AddFilter("Microsoft.AspNetCore.SignalR", LogLevel.Information);
                        })
                        .AddJsonProtocol(options =>
                        {
                            options.PayloadSerializerOptions = JsonConvertersFactory.CreateDefaultJsonConverters(LoggerMotionDetection, LoggerMotionInfo, LoggerJsonVisitor);
                        })
                        .Build();

                    hubConnection.On<MotionDetection>("ReceiveMotionDetection", ReceiveMessage);
                    hubConnection.Closed += CloseHandler;

                    Logger.LogInformation("Starting HubConnection");

                    await hubConnection.StartAsync();

                    Logger.LogInformation("Index Razor Page initialised, listening on signalR hub url => " + hubUrl.ToString());
                }
                catch (Exception e)
                {
                    Logger.LogError(e, "Encountered exception => " + e);
                }
            }
        }

        protected override async Task OnInitializedAsync()
        {
            await Task.CompletedTask;
        }
        #endregion


        #region signalR

        /// <summary>Log signalR connection closing</summary>
        /// <param name="exception">
        /// If an exception occurred while closing then this argument describes the exception
        /// If the signaR connection was closed intentionally by client or server, then this
        /// argument is null
        /// </param>
        private Task CloseHandler(Exception exception)
        {
            if (exception == null)
            {
                Logger.LogInformation("signalR client connection closed");
            }
            else
            {
                Logger.LogInformation($"signalR client closed due to error => {exception.Message}");
            }

            return Task.CompletedTask;
        }

        /// <summary>
        /// Add motion detection notification to repository
        /// </summary>
        /// <param name="message">Motion detection received via signalR</param>
        private void ReceiveMessage(MotionDetection message)
        {
            try
            {
                Logger.LogInformation("Motion detection message received");

                Repository.AddItem(message);

                StateHasChanged();
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "An exception was encountered => " + ex.ToString());
            }
        }
        #endregion
    }
}
使用系统;
使用System.Threading.Tasks;
使用Microsoft.AspNetCore.Components;
使用Microsoft.AspNetCore.signal.Client;
使用Microsoft.Extensions.DependencyInjection;
使用Microsoft.Extensions.Logging;
使用WebApp.Data;
使用WebApp.Data.Serializers.Converters;
使用WebApp.Data.Serializers.Converters.Visitors;
使用WebApp.Repository.Contracts;
名称空间WebApp.Pages
{
公共部分类索引:IAsyncDisposable,IDisposable
{
专用HUB连接HUB连接;
public bool IsConnected=>hubConnection.State==HubConnectionState.Connected;
私有布尔=假;
[注入]
公共导航管理器导航管理器{get;set;}
[注入]
公共IMotionDetectionRepository存储库{get;
using System;
using System.Threading.Tasks;

using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;


namespace WebApp.Realtime.SignalR
{
    /// <summary>
    /// This represents endpoints available on the server, available for the
    /// clients to call
    /// </summary>
    public class MotionHub : Hub<IMotion>
    {
        private bool _disposed = false;
        public ILogger<MotionHub> Logger { get; set; }

        public MotionHub(ILogger<MotionHub> logger) : base()
        {
            Logger = logger;
        }


        public override async Task OnConnectedAsync()
        {
            Logger.LogInformation($"OnConnectedAsync => Connection ID={Context.ConnectionId} : User={Context.User.Identity.Name}");
            await base.OnConnectedAsync();
        }

        public override async Task OnDisconnectedAsync(Exception exception)
        {
            if (exception != null)
            {
                Logger.LogInformation($"OnDisconnectedAsync => Connection ID={Context.ConnectionId} : User={Context.User.Identity.Name} : Exception={exception.Message}");
            }
            else
            {
                Logger.LogInformation($"OnDisconnectedAsync => Connection ID={Context.ConnectionId} : User={Context.User.Identity.Name}");
            }

            await base.OnDisconnectedAsync(exception);
        }

        // Protected implementation of Dispose pattern.
        protected override void Dispose(bool disposing)
        {
            if (_disposed)
            {
                return;
            }

            _disposed = true;

            // Call base class implementation.
            base.Dispose(disposing);
        }
    }
}
  try { await hubConnection.StopAsync(); }
  finally
  {
    await hubConnection.DisposeAsync();
  }