为何检查ModelState时会抛出NullReferenceException?
ModelState空条件运算符仍触发NullReferenceException的原因与解决
问题描述
线上服务偶发空引用异常,报错行指向if (ModelState?.IsValid == false),但该行已使用空条件运算符?.,且本地测试空ModelStateDictionary时该写法不会抛出异常。后续尝试显式检查if (ModelState == null),同样触发空引用异常,问题无法复现但持续出现。
相关代码
using AutoMapper; using JetBrains.Annotations; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Web; using System.Web.Mvc; namespace MyApp.Web.Controllers { public class ServiceCallController : Controller { [NotNull] private readonly IMapper _mapper; [NotNull] private readonly IServiceCallService _service; public ServiceCallController([NotNull] IMapper mapper, [NotNull] IServiceCallService service) { _mapper = mapper; _service = service; } [HttpPost] public ActionResult Index(ServiceCallViewModel vm) { if (ModelState?.IsValid == false)// 该行抛出System.NullReferenceException { Response.StatusCode = (int)HttpStatusCode.BadRequest; Response.TrySkipIisCustomErrors = true; return Json(new { message = string.Join("<br/>", ModelState.GetErrorList()) }); } try { vm.Attachments.RemoveAll(a => a == null); var serviceCall = _mapper.Map<ServiceCallDto>(vm); _service.SubmitServiceCall(serviceCall); return Json(new { url = Url.Action("CallSearch", "Search") }); } catch (Exception ex) { Response.StatusCode = (int)HttpStatusCode.BadRequest; Response.TrySkipIisCustomErrors = true; return Json(new { message = ex.Message }); } } } public class ServiceCallDto { // ...属性定义... } public class ServiceCallViewModel { public List<HttpPostedFileBase> Attachments { get; set; } // ...属性定义... } public interface IServiceCallService { void SubmitServiceCall(ServiceCallDto serviceCall); } public static class ModelStateExtensions { public static List<string> GetErrorList([NotNull] this ModelStateDictionary modelState) => (from item in modelState.Values from error in item.Errors select error.ErrorMessage).ToList(); } }
错误日志
User Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.3 Mobile/15E148 Safari/604.1 System.NullReferenceException: Object reference not set to an instance of an object. at MyApp.Web.Controllers.ServiceCallController.Index(ServiceCallViewModel vm) in C:\Atlassian\bamboo-home\local-working-dir\CMF-MyApp-JOB1\Web\Controllers\ServiceCallController.cs:line 26 at lambda_method(Closure , ControllerBase , Object[] ) at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c.<BeginInvokeSynchronousActionMethod>b__9_0(IAsyncResult asyncResult, ActionInvocation innerInvokeState) at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`2.CallEndDelegate(IAsyncResult asyncResult) at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult) at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass11_0.<InvokeActionMethodFilterAsynchronouslyRecursive>b__0() at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass11_2.<InvokeActionMethodFilterAsynchronouslyRecursive>b__2() at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass3_6.<BeginInvokeAction>b__4() at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass3_1.<BeginInvokeAction>b__1(IAsyncResult asyncResult)
本地测试验证
本地模拟空ModelState时,以下代码未触发异常:
ModelStateDictionary nullState = null; if (nullState?.IsValid == false)// 该行无异常 System.Diagnostics.Debugger.Break(); else if (nullState?.IsValid == true) System.Diagnostics.Debugger.Break(); else if (nullState?.IsValid == null) System.Diagnostics.Debugger.Break();// 调试器停在此行 else System.Diagnostics.Debugger.Break();
核心原因分析
问题的关键在于**ModelState属性的getter逻辑**,而非ModelStateDictionary本身为null。查看ASP.NET MVC的Controller基类源码,ModelState属性的实现如下:
public ModelStateDictionary ModelState { get { if (ControllerContext == null) { throw new InvalidOperationException(MvcResources.Controller_ControllerContextRequired); } return ControllerContext.Controller.ViewData.ModelState; } }
当ControllerContext为null时,访问ModelState属性的getter会直接抛出异常,而非返回null。此时ModelState?.IsValid中的空条件运算符无法生效——因为运算符是在属性值为null时短路,但这里是属性访问本身就抛出了异常。
ControllerContext为空的可能场景:
- 手动实例化Controller但未正确初始化
ControllerContext(正常MVC请求由框架自动初始化,但自定义调用、第三方组件或异常请求路径可能触发) - 请求处理过程中,过滤器、动作调用器等组件意外篡改或清空了
ControllerContext - 移动端请求的特殊处理逻辑导致上下文丢失
解决方案
- 优先检查
ControllerContext是否为空,再访问ModelState:
[HttpPost] public ActionResult Index(ServiceCallViewModel vm) { bool isModelValid = ControllerContext != null && ModelState.IsValid; if (!isModelValid) { Response.StatusCode = (int)HttpStatusCode.BadRequest; Response.TrySkipIisCustomErrors = true; var errorMsg = ControllerContext != null ? string.Join("<br/>", ModelState.GetErrorList()) : "请求上下文异常"; return Json(new { message = errorMsg }); } // 后续逻辑... }
- 排查
ControllerContext为空的根源:- 检查代码中是否存在手动创建Controller实例的逻辑,确保初始化时设置了
ControllerContext - 审计自定义过滤器、动作调用器等组件,确认未非法修改
ControllerContext - 针对移动端请求(错误日志显示为iPhone),排查是否存在特殊路由或请求处理逻辑导致上下文异常
- 检查代码中是否存在手动创建Controller实例的逻辑,确保初始化时设置了
内容的提问来源于stack exchange,提问作者Sarov
相关产品推荐
相关产品推荐

