Blazor Server会话超时遇空引用错误,求无HttpContext实现方案
Blazor Server无HttpContext依赖的会话超时实现方案
核心思路
摒弃传统Session依赖HttpContext的实现方式,利用Blazor Scoped服务生命周期+前端交互监听+SignalR连接状态监控实现20分钟会话超时,每个用户连接对应独立的超时管理实例,完全脱离HttpContext依赖。
1. 实现会话超时管理服务
创建Scoped服务,负责定时器管理、超时事件触发及倒计时更新:
public interface ISessionTimeoutService { event Action OnSessionTimeout; event Action<int> OnCountdownUpdate; void ResetTimeout(); void StartTimeout(int minutes = 20); void StopTimeout(); } public class SessionTimeoutService : ISessionTimeoutService, IDisposable { private Timer _timeoutTimer; private Timer _countdownTimer; private int _remainingSeconds; private int _timeoutMinutes = 20; public event Action OnSessionTimeout; public event Action<int> OnCountdownUpdate; public void StartTimeout(int minutes = 20) { _timeoutMinutes = minutes; ResetTimeout(); } public void ResetTimeout() { _timeoutTimer?.Dispose(); _countdownTimer?.Dispose(); _remainingSeconds = _timeoutMinutes * 60; _countdownTimer = new Timer(UpdateCountdown, null, 0, 1000); _timeoutTimer = new Timer(TriggerTimeout, null, TimeSpan.FromMinutes(_timeoutMinutes), Timeout.InfiniteTimeSpan); } private void UpdateCountdown(object state) { _remainingSeconds--; OnCountdownUpdate?.Invoke(_remainingSeconds); if (_remainingSeconds <= 0) _countdownTimer?.Dispose(); } private void TriggerTimeout(object state) { OnSessionTimeout?.Invoke(); StopTimeout(); } public void StopTimeout() { _timeoutTimer?.Dispose(); _countdownTimer?.Dispose(); _timeoutTimer = null; _countdownTimer = null; } public void Dispose() => StopTimeout(); }
2. 注册服务
在Program.cs中注册Scoped服务(每个用户连接对应一个实例):
builder.Services.AddScoped<ISessionTimeoutService, SessionTimeoutService>();
3. 全局监听用户交互并处理超时
在MainLayout.razor中注入服务,监听用户鼠标/键盘交互以重置超时,同时处理超时事件和倒计时提示:
@inject ISessionTimeoutService SessionTimeoutService @inject NavigationManager NavigationManager @implements IDisposable <div @onmousemove="ResetTimeout" @onkeydown="ResetTimeout" class="main-layout"> @Body @if (_showWarning) { <div class="timeout-warning"> 会话将在@_remainingSeconds秒后超时,点击任意位置重置 </div> } </div> @code { private bool _showWarning; private int _remainingSeconds; protected override void OnInitialized() { SessionTimeoutService.OnSessionTimeout += HandleTimeout; SessionTimeoutService.OnCountdownUpdate += UpdateCountdown; SessionTimeoutService.StartTimeout(20); base.OnInitialized(); } private void ResetTimeout() { SessionTimeoutService.ResetTimeout(); _showWarning = false; } private void UpdateCountdown(int remaining) { _remainingSeconds = remaining; _showWarning = remaining <= 10; // 剩余10秒时显示提示 StateHasChanged(); } private void HandleTimeout() { NavigationManager.NavigateTo("/login", forceLoad: true); } public void Dispose() { SessionTimeoutService.OnSessionTimeout -= HandleTimeout; SessionTimeoutService.OnCountdownUpdate -= UpdateCountdown; SessionTimeoutService.StopTimeout(); } }
4. 补充:监控SignalR连接状态
在App.razor中监听SignalR连接断开事件,处理用户长时间无交互导致的连接中断:
@inject NavigationManager NavigationManager @inject IConnectionStateMonitor ConnectionStateMonitor @implements IDisposable <CascadingAuthenticationState> <Router AppAssembly="@typeof(App).Assembly"> <!-- 原有路由代码保持不变 --> </Router> </CascadingAuthenticationState> @code { protected override void OnInitialized() { ConnectionStateMonitor.ConnectionStateChanged += OnConnectionChanged; base.OnInitialized(); } private void OnConnectionChanged(ConnectionState state) { if (state == ConnectionState.Disconnected) { NavigationManager.NavigateTo("/login", forceLoad: true); } } public void Dispose() { ConnectionStateMonitor.ConnectionStateChanged -= OnConnectionChanged; } }
内容的提问来源于stack exchange,提问作者ahmed abdelaziz
相关产品推荐
相关产品推荐

