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

ASP.NET Core自定义DependencyInjection服务生命周期:特殊实例需求咨询

实现会话级单例服务(Session-scoped Singleton)

你需要的是会话级别的单例服务——同一个用户会话(比如同一个浏览器窗口)内复用实例,不同会话(新窗口、其他设备)则创建新实例。ASP.NET Core原生没有直接提供这个生命周期,但可以通过Session结合自定义工厂实现,以下是具体方案:


1. 先启用Session服务

首先要确保项目中已配置并启用Session,因为会话隔离依赖Session的标识(SessionId)。在Program.cs中添加如下配置:

var builder = WebApplication.CreateBuilder(args);

// 添加控制器/视图服务
builder.Services.AddControllersWithViews();

// 注册Session服务,配置超时和Cookie属性
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(30); // 会话超时时间
    options.Cookie.HttpOnly = true; // 防止前端JS获取Cookie,提升安全性
    options.Cookie.IsEssential = true; // 标记为必要Cookie,符合合规要求
});

// 注册HttpContextAccessor(后续需要用来获取当前会话)
builder.Services.AddHttpContextAccessor();

var app = builder.Build();

// 中间件顺序很重要:UseSession必须放在UseRouting之后、MapControllers之前
app.UseRouting();
app.UseAuthorization();
app.UseSession(); // 启用Session

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

2. 实现会话级服务的工厂逻辑

假设你的目标服务接口和实现类是这样的:

// 服务接口
public interface IUserSessionSingleton
{
    Guid InstanceId { get; }
    void ExecuteBusinessLogic();
}

// 服务实现类
public class UserSessionSingleton : IUserSessionSingleton
{
    // 用InstanceId验证实例是否唯一
    public Guid InstanceId { get; } = Guid.NewGuid();

    public void ExecuteBusinessLogic()
    {
        // 这里写你的业务代码
    }
}

接下来通过Scoped工厂结合Session来实现会话级单例:

// 在Program.cs的Services注册中添加:
builder.Services.AddScoped<IUserSessionSingleton>(sp =>
{
    var httpContext = sp.GetRequiredService<IHttpContextAccessor>().HttpContext;
    var session = httpContext.Session;
    const string sessionKey = "UserSessionSingletonInstance";

    // 尝试从Session中读取已存在的实例
    if (session.TryGetValue(sessionKey, out byte[] data))
    {
        // 反序列化获取实例(需要确保服务类可序列化)
        return JsonSerializer.Deserialize<UserSessionSingleton>(data);
    }

    // 不存在则创建新实例,并存入Session
    var newInstance = new UserSessionSingleton();
    var serializedData = JsonSerializer.SerializeToUtf8Bytes(newInstance);
    session.Set(sessionKey, serializedData);

    return newInstance;
});

3. 无序列化的替代方案(内存缓存)

如果你的服务类无法序列化(比如包含不可序列化的依赖),可以改用内存字典+SessionId的方式存储实例,但要注意清理过期实例避免内存泄漏:

// 先定义一个缓存容器服务
public interface ISessionServiceCache
{
    T GetOrCreate<T>(string sessionId, Func<T> factory) where T : class;
    void CleanupExpiredSessions(IEnumerable<string> validSessionIds);
}

public class SessionServiceCache : ISessionServiceCache
{
    private readonly ConcurrentDictionary<string, object> _cache = new();

    public T GetOrCreate<T>(string sessionId, Func<T> factory) where T : class
    {
        return (T)_cache.GetOrAdd(sessionId, _ => factory());
    }

    public void CleanupExpiredSessions(IEnumerable<string> validSessionIds)
    {
        var validIds = new HashSet<string>(validSessionIds);
        foreach (var key in _cache.Keys.Where(k => !validIds.Contains(k)).ToList())
        {
            _cache.TryRemove(key, out _);
        }
    }
}

// 在Program.cs中注册:
builder.Services.AddSingleton<ISessionServiceCache, SessionServiceCache>();

// 注册服务工厂
builder.Services.AddScoped<IUserSessionSingleton>(sp =>
{
    var cache = sp.GetRequiredService<ISessionServiceCache>();
    var httpContext = sp.GetRequiredService<IHttpContextAccessor>().HttpContext;
    var sessionId = httpContext.Session.Id;

    return cache.GetOrCreate(sessionId, () => new UserSessionSingleton());
});

内存缓存的补充:定期清理过期实例

可以添加一个后台任务,定期清理过期的Session对应的实例:

// 注册后台清理任务
builder.Services.AddHostedService<SessionCacheCleanupService>();

public class SessionCacheCleanupService : BackgroundService
{
    private readonly ISessionServiceCache _cache;

    public SessionCacheCleanupService(ISessionServiceCache cache)
    {
        _cache = cache;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // 模拟获取所有有效的SessionId(实际项目中需根据Session存储方式实现)
            var validSessionIds = new List<string>();
            // 调用清理方法
            _cache.CleanupExpiredSessions(validSessionIds);

            // 每30分钟清理一次
            await Task.Delay(TimeSpan.FromMinutes(30), stoppingToken);
        }
    }
}

4. 关键注意事项

  • 会话隔离验证:隐身窗口或其他设备访问时,会生成新的SessionId,因此会自动创建新的服务实例,符合你的需求。
  • 多服务器部署:如果是集群环境,建议用分布式Session(比如Redis),确保SessionId在服务器间共享,同时服务实例的存储也要改用分布式缓存(比如Redis序列化存储)。
  • 内存泄漏风险:内存缓存方案必须定期清理过期实例,否则会占用大量内存。
  • 服务依赖处理:如果你的服务需要注入其他依赖,可以在工厂方法中通过sp.GetRequiredService<>来获取,比如:
    return cache.GetOrCreate(sessionId, () => 
        new UserSessionSingleton(sp.GetRequiredService<IOtherService>()));
    

内容的提问来源于stack exchange,提问作者DmitryB

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 21:30:33