如何创建Blazor全局触发的自定义会话超时中间件
Blazor全局触发会话超时检查的解决方案
问题概述
需要在Blazor应用中,用户进行任意页面操作或调用特定类对象时自动触发会话超时检查;此前尝试通过脚本捕获点击/按键事件导致误登出,且无法使用Microsoft.Session实现超时逻辑。
现有中间件的问题分析
- HTTP中间件局限性:Blazor(尤其是Server模式)的交互多基于SignalR WebSocket,而非普通HTTP请求,原有中间件仅会在页面初始加载或刷新时触发,无法捕获客户端的实时交互动作。
- 多用户冲突风险:
ATMTreeViewRepository若为单例服务,会导致所有用户共享同一个sessionTime,造成会话状态混乱。 - NavigationManager使用错误:中间件运行在服务器端HTTP管道中,直接获取
NavigationManager(客户端服务)会引发上下文不一致问题。
解决方案步骤
1. 创建Scoped会话状态服务
用于存储每个用户独立的会话活动时间,避免多用户冲突:
public class SessionStateService { // 记录最后一次活动时间 public DateTime? LastActivityTime { get; set; } // 超时阈值(分钟) public const int TimeoutMinutes = 5; }
在Program.cs中注册为Scoped服务:
builder.Services.AddScoped<SessionStateService>();
2. 实现会话超时管理服务
负责定时检查超时、重置会话状态,并处理导航逻辑:
public class SessionTimeoutService : IDisposable { private readonly SessionStateService _sessionState; private readonly NavigationManager _navigationManager; private Timer _timeoutTimer; public SessionTimeoutService(SessionStateService sessionState, NavigationManager navigationManager) { _sessionState = sessionState; _navigationManager = navigationManager; // 初始化超时定时器 ResetTimeoutTimer(); } // 重置会话活动时间并重启定时器 public void ResetSessionActivity() { _sessionState.LastActivityTime = DateTime.Now; ResetTimeoutTimer(); } private void ResetTimeoutTimer() { _timeoutTimer?.Dispose(); // 设置定时器:超时后触发检查,仅执行一次 _timeoutTimer = new Timer(CheckTimeout, null, TimeSpan.FromMinutes(SessionStateService.TimeoutMinutes), Timeout.InfiniteTimeSpan); } private void CheckTimeout(object state) { if (_sessionState.LastActivityTime == null) return; var elapsedMinutes = DateTime.Now.Subtract(_sessionState.LastActivityTime.Value).TotalMinutes; if (elapsedMinutes > SessionStateService.TimeoutMinutes) { // 跳转到首页(强制重载以清空状态) _navigationManager.NavigateTo(_navigationManager.BaseUri, forceLoad: true); } } public void Dispose() { _timeoutTimer?.Dispose(); } }
注册为Scoped服务:
builder.Services.AddScoped<SessionTimeoutService>();
3. 全局捕获用户交互动作
在MainLayout.razor中添加全局事件监听,用户的点击、按键、鼠标移动操作都会重置会话状态:
@inherits LayoutComponentBase @inject SessionTimeoutService SessionTimeoutService @implements IDisposable <!-- 监听全局用户交互事件 --> <div @onclick="ResetSession" @onkeydown="ResetSession" @onmouseover="ResetSession"> @Body </div> @code { private void ResetSession() { SessionTimeoutService.ResetSessionActivity(); } public void Dispose() { ((IDisposable)SessionTimeoutService)?.Dispose(); } }
4. 特定类对象调用时自动触发
在需要触发超时检查的类(如ATMTreeViewRepository)中注入SessionTimeoutService,在方法执行前重置会话状态:
public class ATMTreeViewRepository { private readonly SessionTimeoutService _sessionTimeoutService; public ATMTreeViewRepository(SessionTimeoutService sessionTimeoutService) { _sessionTimeoutService = sessionTimeoutService; } // 示例方法:调用时自动重置会话 public async Task LoadTreeData() { _sessionTimeoutService.ResetSessionActivity(); // 原有业务逻辑... } }
方案优势
- 避免脚本捕获事件的误触发问题:仅响应用户主动交互动作
- 基于Scoped服务实现用户隔离:每个用户拥有独立的会话状态
- 实时检查超时:通过定时器主动监控,无需依赖HTTP请求
- 灵活扩展:可轻松添加更多触发场景(如特定组件交互)
内容的提问来源于stack exchange,提问作者jabbar007
相关产品推荐
相关产品推荐

