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
相关产品推荐
相关产品推荐

