Blazor WebAssembly中如何全局检测客户端与服务端的连接状态
Blazor WASM全局连接状态检测实现方案
1. 新增网络状态检测基础服务
首先实现浏览器原生网络状态监听,处理客户端本地断网场景:
public interface INetworkStatusService { bool IsOnline { get; } event Action<bool> NetworkStatusChanged; Task InitializeAsync(); } public class NetworkStatusService : INetworkStatusService, IAsyncDisposable { private readonly IJSRuntime _jsRuntime; private DotNetObjectReference<NetworkStatusService>? _dotNetRef; public bool IsOnline { get; private set; } public event Action<bool>? NetworkStatusChanged; public NetworkStatusService(IJSRuntime jsRuntime) { _jsRuntime = jsRuntime; } public async Task InitializeAsync() { _dotNetRef = DotNetObjectReference.Create(this); IsOnline = await _jsRuntime.InvokeAsync<bool>("navigator.onLine"); await _jsRuntime.InvokeVoidAsync("window.registerNetworkStatusListeners", _dotNetRef); } [JSInvokable] public void OnNetworkStatusChanged(bool isOnline) { IsOnline = isOnline; NetworkStatusChanged?.Invoke(isOnline); } public async ValueTask DisposeAsync() { if (_dotNetRef != null) { await _jsRuntime.InvokeVoidAsync("window.removeNetworkStatusListeners", _dotNetRef); _dotNetRef.Dispose(); } } }
添加对应的JS监听代码,放在wwwroot/index.html的<script>标签里:
let onlineHandler, offlineHandler; window.registerNetworkStatusListeners = (dotNetRef) => { onlineHandler = () => dotNetRef.invokeMethodAsync('OnNetworkStatusChanged', true); offlineHandler = () => dotNetRef.invokeMethodAsync('OnNetworkStatusChanged', false); window.addEventListener('online', onlineHandler); window.addEventListener('offline', offlineHandler); }; window.removeNetworkStatusListeners = (dotNetRef) => { window.removeEventListener('online', onlineHandler); window.removeEventListener('offline', offlineHandler); };
2. 新增SignalR后端连通性检测服务
处理客户端网络正常但后端服务停止/无法连接的场景,通过SignalR长连接心跳实现:
public interface IServerConnectionService { bool IsServerConnected { get; } event Action<bool> ServerConnectionStatusChanged; Task StartAsync(); Task StopAsync(); } public class ServerConnectionService : IServerConnectionService, IAsyncDisposable { private readonly NavigationManager _navManager; private HubConnection? _hubConnection; public bool IsServerConnected { get; private set; } public event Action<bool>? ServerConnectionStatusChanged; public ServerConnectionService(NavigationManager navManager) { _navManager = navManager; } public async Task StartAsync() { _hubConnection = new HubConnectionBuilder() .WithUrl(_navManager.ToAbsoluteUri("/healthHub")) .WithAutomaticReconnect(new[] { TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10) }) .Build(); _hubConnection.Closed += OnConnectionClosed; _hubConnection.Reconnected += OnReconnected; _hubConnection.Reconnecting += OnReconnecting; try { await _hubConnection.StartAsync(); IsServerConnected = true; ServerConnectionStatusChanged?.Invoke(true); } catch { IsServerConnected = false; ServerConnectionStatusChanged?.Invoke(false); } } private Task OnReconnecting(Exception? arg) { IsServerConnected = false; ServerConnectionStatusChanged?.Invoke(false); return Task.CompletedTask; } private Task OnReconnected(string? arg) { IsServerConnected = true; ServerConnectionStatusChanged?.Invoke(true); return Task.CompletedTask; } private Task OnConnectionClosed(Exception? arg) { IsServerConnected = false; ServerConnectionStatusChanged?.Invoke(false); return Task.CompletedTask; } public async Task StopAsync() { if (_hubConnection != null) { await _hubConnection.StopAsync(); } } public async ValueTask DisposeAsync() { if (_hubConnection != null) { await _hubConnection.DisposeAsync(); } } }
- 后端需要同步添加一个空的HealthHub,不需要实现任何方法,仅用于维持连接:
// 后端ASP.NET Core项目中添加 public class HealthHub : Hub { } // Program.cs中注册 builder.Services.AddSignalR(); // 中间件配置部分添加 app.MapHub<HealthHub>("/healthHub");
3. 改造现有HttpInterceptorService
整合上述两个服务的状态,处理连接丢失场景:
public class HttpInterceptorService { private readonly HttpClientInterceptor _interceptor; private readonly NavigationManager _navManager; private readonly RefreshTokenService _refreshTokenService; // 新增注入 private readonly INetworkStatusService _networkStatusService; private readonly IServerConnectionService _serverConnectionService; public HttpInterceptorService(HttpClientInterceptor interceptor, NavigationManager navManager, RefreshTokenService refreshTokenService, INetworkStatusService networkStatusService, IServerConnectionService serverConnectionService) { _interceptor = interceptor; _navManager = navManager; _refreshTokenService = refreshTokenService; _networkStatusService = networkStatusService; _serverConnectionService = serverConnectionService; // 监听状态变化,全局提示或跳转 _networkStatusService.NetworkStatusChanged += OnStatusChanged; _serverConnectionService.ServerConnectionStatusChanged += OnStatusChanged; } private void OnStatusChanged(bool isConnected) { if (!isConnected) { _navManager.NavigateTo("/PageError?message=网络连接已断开,请检查网络或稍后重试"); } } public void RegisterEvent() => _interceptor.AfterSend += HandleResponse; public void RegisterBeforeSendEvent() => _interceptor.BeforeSendAsync += InterceptBeforeSendAsync; public void DisposeEvent() { _interceptor.AfterSend -= HandleResponse; _interceptor.BeforeSendAsync -= InterceptBeforeSendAsync; _networkStatusService.NetworkStatusChanged -= OnStatusChanged; _serverConnectionService.ServerConnectionStatusChanged -= OnStatusChanged; } private async Task InterceptBeforeSendAsync(object sender, HttpClientInterceptorEventArgs e) { // 发送请求前先检查连接状态,已断开直接拦截 if (!_networkStatusService.IsOnline || !_serverConnectionService.IsServerConnected) { _navManager.NavigateTo("/PageError?message=网络连接不可用,请检查后重试"); throw new HttpResponseException("网络连接不可用"); } var absolutePath = e.Request.RequestUri.AbsolutePath; if (!absolutePath.Contains("token") && !absolutePath.Contains("account")) { var token = await _refreshTokenService.TryRefreshToken(); if (!string.IsNullOrEmpty(token)) { e.Request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token); } } } private void HandleResponse(object sender, HttpClientInterceptorEventArgs e) { if (e.Response == null) { _navManager.NavigateTo("/PageError?message=服务器无法连接,请稍后重试"); throw new HttpResponseException("Server not available."); } var message = ""; if (!e.Response.IsSuccessStatusCode) { switch (e.Response.StatusCode) { case HttpStatusCode.NotFound: _navManager.NavigateTo("/Page404"); break; case HttpStatusCode.BadRequest: break; case HttpStatusCode.Unauthorized: _navManager.NavigateTo("/unauthorized"); break; default: _navManager.NavigateTo("/PageError"); break; } throw new HttpResponseException(message); } } }
4. 全局注册和初始化
在Blazor WASM项目的Program.cs中注册服务:
builder.Services.AddSingleton<INetworkStatusService, NetworkStatusService>(); builder.Services.AddSingleton<IServerConnectionService, ServerConnectionService>(); builder.Services.AddScoped<HttpInterceptorService>(); // 原有HttpClientInterceptor等注册保留
在App.razor中初始化服务,保证全局生效:
@inject INetworkStatusService NetworkStatusService @inject IServerConnectionService ServerConnectionService @inject HttpInterceptorService HttpInterceptorService @implements IAsyncDisposable <Router AppAssembly="@typeof(App).Assembly"> <!-- 原有路由配置保留 --> </Router> @code { protected override async Task OnInitializedAsync() { await NetworkStatusService.InitializeAsync(); await ServerConnectionService.StartAsync(); HttpInterceptorService.RegisterBeforeSendEvent(); HttpInterceptorService.RegisterEvent(); } public async ValueTask DisposeAsync() { HttpInterceptorService.DisposeEvent(); await NetworkStatusService.DisposeAsync(); await ServerConnectionService.DisposeAsync(); } }
内容的提问来源于stack exchange,提问作者Ali
相关产品推荐
相关产品推荐

