ASP.NET MVC:如何在尝试修改只读Session时抛出异常
ASP.NET MVC:如何在尝试修改只读Session时抛出异常
这个需求真的很实用——静默丢弃Session修改太容易留坑了,我之前排查bug的时候也吃过这个亏。下面给你分享两种可行的实现方案,你可以根据项目情况选择:
方案一:自定义Session包装类 + HttpModule全局拦截
这个方案的核心思路是包装原生的Session对象,在所有修改操作前检查当前Session是否处于只读状态,如果是就直接抛出异常。
步骤1:实现只读感知的Session包装类
我们继承HttpSessionStateWrapper,重写所有会修改Session的方法和索引器,在执行实际操作前先校验状态:
public class ReadOnlyAwareSessionStateWrapper : HttpSessionStateWrapper { private readonly bool _isReadOnly; public ReadOnlyAwareSessionStateWrapper(HttpSessionState session, bool isReadOnly) : base(session) { _isReadOnly = isReadOnly; } // 统一的只读校验方法 private void ThrowIfReadOnly() { if (_isReadOnly) { throw new InvalidOperationException("当前Session为只读状态,禁止执行修改操作!"); } } // 重写所有修改Session的方法 public override void Add(string name, object value) { ThrowIfReadOnly(); base.Add(name, value); } public override void Remove(string name) { ThrowIfReadOnly(); base.Remove(name); } public override void Clear() { ThrowIfReadOnly(); base.Clear(); } public override void RemoveAll() { ThrowIfReadOnly(); base.RemoveAll(); } public override void RemoveAt(int index) { ThrowIfReadOnly(); base.RemoveAt(index); } // 重写索引器的set方法 public override object this[string name] { get => base[name]; set { ThrowIfReadOnly(); base[name] = value; } } public override object this[int index] { get => base[index]; set { ThrowIfReadOnly(); base[index] = value; } } }
步骤2:实现HttpModule替换Session对象
通过IHttpModule在请求生命周期中,自动识别标记了ReadOnly SessionState的控制器,并替换其Session为我们的包装类:
public class ReadOnlySessionEnforcementModule : IHttpModule { public void Init(HttpApplication context) { // 绑定AcquireRequestState事件,此时Session已可用 context.AcquireRequestState += OnAcquireRequestState; } private void OnAcquireRequestState(object sender, EventArgs e) { var app = (HttpApplication)sender; var httpContext = app.Context; // 如果Session未初始化,直接跳过 if (httpContext.Session == null) return; // 获取当前请求的控制器信息 var routeData = RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext)); if (routeData?.Values["controller"] == null) return; var controllerName = routeData.Values["controller"].ToString(); // 注意替换成你项目的控制器命名空间 var controllerType = Type.GetType($"YourProjectNamespace.Controllers.{controllerName}Controller"); if (controllerType == null) return; // 检查控制器是否标记了ReadOnly的SessionStateAttribute var sessionStateAttr = controllerType.GetCustomAttribute<SessionStateAttribute>(); if (sessionStateAttr?.Behavior == SessionStateBehavior.ReadOnly) { // 替换原生Session为自定义包装类 httpContext.Session = new ReadOnlyAwareSessionStateWrapper(httpContext.Session, true); } } public void Dispose() { // 无需额外清理,空实现即可 } }
步骤3:注册HttpModule
在web.config中注册我们的模块,根据你的IIS模式选择对应配置:
IIS集成模式(推荐)
<system.webServer> <modules> <add name="ReadOnlySessionEnforcementModule" type="YourProjectNamespace.ReadOnlySessionEnforcementModule" /> </modules> </system.webServer>
IIS经典模式
<system.web> <httpModules> <add name="ReadOnlySessionEnforcementModule" type="YourProjectNamespace.ReadOnlySessionEnforcementModule" /> </httpModules> </system.web>
方案二:使用ActionFilter局部拦截
如果不想全局生效,也可以用ActionFilter针对单个控制器或Action做拦截:
public class EnforceReadOnlySessionAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var sessionStateAttr = filterContext.Controller.GetType() .GetCustomAttribute<SessionStateAttribute>(); if (sessionStateAttr?.Behavior == SessionStateBehavior.ReadOnly) { // 替换Session为包装类 filterContext.HttpContext.Session = new ReadOnlyAwareSessionStateWrapper( filterContext.HttpContext.Session, true); } base.OnActionExecuting(filterContext); } }
然后在目标控制器上添加这个过滤器:
[SessionState(SessionStateBehavior.ReadOnly)] [EnforceReadOnlySession] public class HomeController : Controller { // ... }
效果验证
现在只要在标记了ReadOnly的控制器里尝试修改Session,比如执行Session["Test"] = "Value";,就会立刻抛出InvalidOperationException,而不是静默丢弃修改,能帮你在开发阶段及时发现问题。
备注:内容来源于stack exchange,提问作者Virus721
相关产品推荐
相关产品推荐

