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

关于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:17:25