如何确保MVC中间件逻辑在每个Session中仅执行一次?
优化建议与替代方案
你的当前实现逻辑是通顺的,能满足“每个Session仅执行一次初始化”的需求,但可以从代码可读性、并发安全性、职责分离这几个维度做优化,以下是具体方案:
1. 简化Session判断逻辑
当前用context.Session.Get("sessionWasInitialized") != null的方式可以简化,直接用ISession.GetString方法,语义更清晰:
public async Task InvokeAsync(HttpContext context) { if (!string.IsNullOrEmpty(context.Session.GetString("sessionWasInitialized"))) { await _next(context); return; } context.Session.SetString("sessionWasInitialized", "true"); // 从数据库获取用户数据并写入Session的逻辑 await _next(context); }
2. 解决并发请求下的重复执行问题
如果同一个Session同时发起多个请求(比如页面加载时的并行资源请求),可能会出现多个请求同时进入初始化逻辑的情况,导致重复查询数据库。可以通过本地锁+Session二次检查来避免:
private static readonly object _lockObj = new object(); public async Task InvokeAsync(HttpContext context) { var sessionKey = "sessionWasInitialized"; if (!string.IsNullOrEmpty(context.Session.GetString(sessionKey))) { await _next(context); return; } // 加锁避免同Session并发请求重复执行 lock (_lockObj) { // 再次检查,因为锁等待期间可能已经被其他请求初始化完成 if (!string.IsNullOrEmpty(context.Session.GetString(sessionKey))) { await _next(context); return; } context.Session.SetString(sessionKey, "true"); } // 数据库查询与Session写入逻辑 await _next(context); }
注:这里的本地锁适用于单服务器部署场景,如果是多服务器集群,需要改用分布式锁来保证一致性。
3. 职责分离:将初始化逻辑抽离为独立服务
把数据库查询、Session写入的逻辑从中间件中抽离成单独的服务,让中间件只负责“判断是否需要初始化”的逻辑,代码更符合单一职责原则:
首先定义初始化服务:
public interface ISessionUserInitializer { Task InitializeUserSessionAsync(HttpContext context); } public class SessionUserInitializer : ISessionUserInitializer { private readonly IUserRepository _userRepository; public SessionUserInitializer(IUserRepository userRepository) { _userRepository = userRepository; } public async Task InitializeUserSessionAsync(HttpContext context) { // 从数据库获取用户数据 var userData = await _userRepository.GetCurrentUserDataAsync(context.User.Identity.Name); // 写入Session context.Session.SetString("CurrentUser", JsonSerializer.Serialize(userData)); } }
然后在中间件中注入并使用:
public class SessionInitializerMiddleware { private readonly RequestDelegate _next; private readonly ISessionUserInitializer _initializer; private static readonly object _lockObj = new object(); public SessionInitializerMiddleware(RequestDelegate next, ISessionUserInitializer initializer) { _next = next; _initializer = initializer; } public async Task InvokeAsync(HttpContext context) { var sessionKey = "sessionWasInitialized"; if (!string.IsNullOrEmpty(context.Session.GetString(sessionKey))) { await _next(context); return; } lock (_lockObj) { if (!string.IsNullOrEmpty(context.Session.GetString(sessionKey))) { await _next(context); return; } context.Session.SetString(sessionKey, "true"); } await _initializer.InitializeUserSessionAsync(context); await _next(context); } }
4. 替代方案:利用Session扩展方法封装判断逻辑
可以封装一个Session扩展方法,让判断逻辑复用性更强,代码更简洁:
public static class SessionExtensions { public static bool IsSessionInitialized(this ISession session) { return !string.IsNullOrEmpty(session.GetString("sessionWasInitialized")); } public static void MarkSessionAsInitialized(this ISession session) { session.SetString("sessionWasInitialized", "true"); } }
然后中间件中使用:
public async Task InvokeAsync(HttpContext context) { if (context.Session.IsSessionInitialized()) { await _next(context); return; } context.Session.MarkSessionAsInitialized(); // 数据库查询与Session写入逻辑 await _next(context); }
内容的提问来源于stack exchange,提问作者Jonathan Applebaum
相关产品推荐
相关产品推荐

