Asp.net core 关闭/断开ASP.NET Core Signal客户端连接的正确方法是什么?
我是一个新用户,正在努力从ASP.NET Core Blazor服务器页面优雅地关闭辅助信号器客户端 我正在Blazor服务器页面的第一次呈现上设置一个辅助信号器客户端连接。当页面通过浏览器选项卡关闭时,我正在尝试关闭此辅助信号器客户端连接 在编写时,通过浏览器选项卡关闭页面时,似乎不会触发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都在关闭浏览器选项卡时触发
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();
}