关于C# Orchard会话销毁EventHandler及会话事件控制接口的问询
好问题!在Orchard框架(尤其是现在主流的Orchard Core,基于ASP.NET Core构建)里,虽然没有直接给你一个开箱即用的「会话非活跃时长检测」接口,但咱们可以结合ASP.NET Core的会话机制和Orchard本身的扩展能力,完美实现你要的需求。下面我给你拆解两种实用的方案,你可以根据自己的业务场景选:
方案一:结合会话中间件与Orchard后台服务,主动检测非活跃会话
这个方案适合需要定期扫描所有会话,一旦发现某个会话超过指定非活跃时长就执行任务的场景。
1. 跟踪会话的最后活跃时间
咱们得在每次用户发起请求时,更新会话里的最后活跃时间戳——这一步可以通过实现Orchard的IRequestFilter接口来拦截请求完成:
public class SessionActivityFilter : IRequestFilter { private readonly IHttpContextAccessor _httpContextAccessor; public SessionActivityFilter(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { var session = _httpContextAccessor.HttpContext.Session; if (session.IsAvailable) { // 用ISO格式存储UTC时间,避免时区问题 session.SetString("LastActiveTime", DateTime.UtcNow.ToString("o")); } return next(); } // 下面两个方法空实现即可,满足接口要求 public Task OnAuthorizationAsync(AuthorizationFilterContext context) => Task.CompletedTask; public Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) => next(); }
记得在模块的Startup类里注册这个过滤器:
services.AddScoped<IRequestFilter, SessionActivityFilter>();
2. 后台服务定期检查并处理非活跃会话
实现一个BackgroundService(Orchard支持ASP.NET Core的后台服务机制),定期扫描所有会话,判断是否超过你设定的非活跃时长:
public class InactiveSessionChecker : BackgroundService { private readonly ISessionStore _sessionStore; // 自定义非活跃时长,比如2小时 private readonly TimeSpan _inactiveThreshold = TimeSpan.FromHours(2); public InactiveSessionChecker(ISessionStore sessionStore) { _sessionStore = sessionStore; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { // 注意:默认SessionStore没有公开遍历所有会话的方法 // 如果是分布式部署,建议用Redis存会话活跃时间,用SCAN命令获取所有会话键 var allSessionKeys = await GetAllSessionKeysFromDistributedCache(); foreach (var key in allSessionKeys) { var session = await _sessionStore.GetAsync(key, stoppingToken); if (session == null) continue; if (session.TryGetValue("LastActiveTime", out var lastActiveBytes)) { var lastActiveTime = DateTime.ParseExact( Encoding.UTF8.GetString(lastActiveBytes), "o", CultureInfo.InvariantCulture ); if (DateTime.UtcNow - lastActiveTime > _inactiveThreshold) { // 执行你的业务任务,比如清理临时数据、记录日志等 await ExecuteInactiveSessionTask(session.Id); // 销毁这个非活跃会话 await _sessionStore.RemoveAsync(key, stoppingToken); } } } // 每隔10分钟检查一次,可根据业务调整频率 await Task.Delay(TimeSpan.FromMinutes(10), stoppingToken); } } // 这里需要根据你用的分布式缓存实现获取会话键的逻辑 private async Task<IEnumerable<string>> GetAllSessionKeysFromDistributedCache() { // 示例:如果用Redis,可以用StackExchange.Redis的SCAN命令 // var redis = ConnectionMultiplexer.Connect("你的Redis连接串"); // var db = redis.GetDatabase(); // var keys = await db.ScanAsync(pattern: "Session:*").ToListAsync(); // return keys.Select(k => k.ToString()); return new List<string>(); } private async Task ExecuteInactiveSessionTask(string sessionId) { // 替换成你的实际任务逻辑 Console.WriteLine($"会话 {sessionId} 已超过{_inactiveThreshold}未活跃,执行清理任务..."); } }
同样在Startup类里注册这个后台服务:
services.AddHostedService<InactiveSessionChecker>();
方案二:监听会话销毁事件,事后校验非活跃时长
如果你的需求是在会话销毁时,先判断它是否已经非活跃了指定时长,再执行任务,那可以结合Orchard的事件总线来实现。
1. 同样先跟踪会话活跃时间
和方案一的第一步完全一样,用IRequestFilter更新会话的LastActiveTime。
2. 定义并监听会话销毁事件
首先定义一个自定义事件类,用来传递会话销毁的相关信息:
public class SessionDestroyedEvent { public string SessionId { get; set; } public DateTime LastActiveTime { get; set; } }
然后实现事件处理器,在事件触发时校验非活跃时长并执行任务:
public class SessionDestroyedHandler : IEventHandler<SessionDestroyedEvent> { private readonly TimeSpan _inactiveThreshold = TimeSpan.FromHours(2); public Task HandleAsync(SessionDestroyedEvent evnt) { if (DateTime.UtcNow - evnt.LastActiveTime > _inactiveThreshold) { // 符合非活跃时长条件,执行任务 return ExecutePostDestroyTask(evnt.SessionId); } return Task.CompletedTask; } private async Task ExecutePostDestroyTask(string sessionId) { // 你的业务任务逻辑 } }
注册这个事件处理器:
services.AddScoped<IEventHandler<SessionDestroyedEvent>, SessionDestroyedHandler>();
3. 在会话销毁时触发事件
咱们需要自定义一个SessionStore,覆盖默认的移除会话方法,在会话被销毁时发布事件:
public class CustomSessionStore : ISessionStore { private readonly ISessionStore _innerStore; private readonly IEventBus _eventBus; public CustomSessionStore(ISessionStore innerStore, IEventBus eventBus) { _innerStore = innerStore; _eventBus = eventBus; } public async Task<ISession> CreateAsync(string sessionId, TimeSpan idleTimeout, CancellationToken cancellationToken) { return await _innerStore.CreateAsync(sessionId, idleTimeout, cancellationToken); } public async Task<ISession> GetAsync(string sessionId, CancellationToken cancellationToken) { return await _innerStore.GetAsync(sessionId, cancellationToken); } public async Task RemoveAsync(string sessionId, CancellationToken cancellationToken) { var session = await _innerStore.GetAsync(sessionId, cancellationToken); var lastActiveTime = DateTime.UtcNow; if (session != null && session.TryGetValue("LastActiveTime", out var bytes)) { lastActiveTime = DateTime.ParseExact(Encoding.UTF8.GetString(bytes), "o", CultureInfo.InvariantCulture); } // 发布会话销毁事件 await _eventBus.PublishAsync(new SessionDestroyedEvent { SessionId = sessionId, LastActiveTime = lastActiveTime }, cancellationToken); // 执行原始的会话移除操作 await _innerStore.RemoveAsync(sessionId, cancellationToken); } }
最后替换掉默认的SessionStore:
services.Replace(ServiceDescriptor.Scoped<ISessionStore, CustomSessionStore>());
额外注意事项
- 如果你用的是Orchard 1.x(基于ASP.NET MVC),核心逻辑类似,但需要改用Orchard 1.x的事件系统和ASP.NET MVC的会话机制。
- 分布式部署场景下,必须用Redis这类分布式缓存存储会话数据,否则后台服务无法跨节点获取会话信息。
- 检查频率和非活跃时长要根据业务需求调整,避免过于频繁的扫描影响系统性能。
内容的提问来源于stack exchange,提问作者buff
相关产品推荐
相关产品推荐

