You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何检查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
  • 移动端请求的特殊处理逻辑导致上下文丢失

解决方案

  1. 优先检查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 });
    }

    // 后续逻辑...
}
  1. 排查ControllerContext为空的根源:
    • 检查代码中是否存在手动创建Controller实例的逻辑,确保初始化时设置了ControllerContext
    • 审计自定义过滤器、动作调用器等组件,确认未非法修改ControllerContext
    • 针对移动端请求(错误日志显示为iPhone),排查是否存在特殊路由或请求处理逻辑导致上下文异常

内容的提问来源于stack exchange,提问作者Sarov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 23:22:56