SQL Server会话状态下手动实例化控制器访问Session出现空引用异常
问题根本原因
这个空引用问题和你切换到SQL Server存储Session的模式没有直接关系,换存储只是让之前藏着的代码问题暴露出来而已,核心问题出在手动实例化跨控制器调用的写法上:
- 控制器类里能直接访问的
Session属性,是从基类Controller继承来的,它不是独立存在的全局对象,本质是附着在当前请求的HttpContext下面的子属性。 - 正常的控制器实例,是ASP.NET请求管道在处理HTTP请求的过程中自动创建的,创建完成后会自动把当前请求的
HttpContext(包括关联的Session、登录用户身份、路由解析结果等)绑定到控制器实例上,所以你在所属控制器的正常方法里读Session一点问题都没有。 - 你手动
new SalesInvoiceSettingController()创建的对象,完全没有经过请求管道的初始化流程,它内部的HttpContext是空的,连带下面挂的Session自然也是null,直接调用.ToString()就必然抛“未将对象引用设置到对象的实例”的错误。
之前用InProc模式的时候没出问题,只是因为你从来没在这个手动创建的空壳控制器里真正读过Session,并非这种写法本身是正确的。
可落地的解决方法
- 最优先的修正方向:彻底废弃手动跨控制器实例化调用的写法
控制器的定位是接收、响应HTTP请求的入口,不是给你当通用业务类随便实例化调用的,这是MVC/WebAPI框架下的典型反模式,长期这么写,早晚会遇到模型状态丢失、权限校验失效、路由解析错误等一堆奇奇怪怪的上下文问题。 - 最优的重构方案:把跨控制器复用的逻辑抽成独立的业务服务
把两个控制器都要用到的发票设置保存、读取当前登录用户Session的公共逻辑,抽成独立的服务类,注册到项目的依赖注入容器里,两个控制器都从容器中拿到同一个服务实例来使用,从根源上避免手动创建空壳对象的问题。
参考实现代码:
控制器层的代码调整为通过构造函数注入服务,不再手动创建其他控制器对象:// 独立公共服务,通过内置的IHttpContextAccessor获取当前请求上下文 public class InvoiceSettingService { private readonly IHttpContextAccessor _httpContextAccessor; public InvoiceSettingService(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } // 统一读取当前登录用户名的逻辑,所有调用方共用 public string GetCurrentUserName() { return _httpContextAccessor.HttpContext?.Session?.GetString("UserName"); } public string SaveInvoiceSettings() { var currentUser = GetCurrentUserName(); // 把原SaveInvoiceSettings方法里的所有业务逻辑迁移到这里即可 } }public class CompanyController : Controller { private readonly InvoiceSettingService _invoiceSettingService; // 构造函数注入已经初始化好的服务实例 public CompanyController(InvoiceSettingService invoiceSettingService) { _invoiceSettingService = invoiceSettingService; } public string AddNewCompany() { // 直接调用服务方法,无需手动new其他控制器 _invoiceSettingService.SaveInvoiceSettings(); } } - 临时过渡的兜底方案(不推荐长期使用)
如果短期内没有条件完成代码重构,可以在手动创建控制器实例后,手动把当前请求的上下文绑定到新实例上,让它能正常访问到关联的Session对象:
必须明确的是,这种写法只是临时绕开了空引用的报错,因为它没有完整初始化控制器的所有绑定属性(比如模型校验状态、路由匹配信息、用户权限上下文等),后续依然可能触发其他未知的运行时异常,只能作为重构前的临时过渡手段。public String AddNewCompany() { SalesInvoiceSettingController invoiceSetting = new SalesInvoiceSettingController(); // 手动给新实例绑定当前请求的上下文 invoiceSetting.ControllerContext = new ControllerContext { HttpContext = this.HttpContext }; // 绑定完成后再调用方法,即可正常读取Session值 invoiceSetting.SaveInvoiceSettings(); }
内容的提问来源于stack exchange,提问作者Haris Tasawar
相关产品推荐
相关产品推荐

