You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 03:48:13