UPDATE: To avoid the problem, I tried explicitly checking for null, thinking that surely there's no way it could possible still throw the exception. It does. The following line throws a System.NullReferenceException:
if (ModelState == null)
How?!
We received an automated error report, showing that a particular line is throwing a NullReferenceException... but I cannot figure out how that particular line could possibly do that.
The line in question is if (ModelState?.IsValid == false). But... I've got a null conditional operator on there. How could it throw a NullReferenceException?
We haven't been able to figure out how to reproduce the error intentionally, but it keeps happening every now and then.
Here is the code (slightly pared down, but the relevant method left untouched:)
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)//This line throws "System.NullReferenceException: Object reference not set to an instance of an object."
{
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
{
// ...bunch of properties...
}
public class ServiceCallViewModel
{
public List<HttpPostedFileBase> Attachments { get; set; }
// ...bunch of properties...
}
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();
}
}
And here is the automated error email we received:
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)
To further illustrate the issue:
My understanding is that ModelState?.IsValid evaluates to a bool? aka a Nullable<bool>. Which c#, as demonstrated in the snippet below, is able to compare against false even when null. So... how could that line possibly throw a NullReferenceException?
ModelStateDictionary nullState = null;
if (nullState?.IsValid == false)//Unlike what's happening in the real code in the wild, this line does NOT throw an error.
System.Diagnostics.Debugger.Break();
else if (nullState?.IsValid == true)
System.Diagnostics.Debugger.Break();
else if (nullState?.IsValid == null)
System.Diagnostics.Debugger.Break();//The debugger stops on this line.
else
System.Diagnostics.Debugger.Break();
public ModelStateDictionary ModelState { get; }. I can't see the code of the property getter since it's written by Microsoft.if (ModelState == null)check because the previous code was throwing the NullReferenceException onModelState?.IsValidand when I did the stacktrace's line number changed accordingly, so yes, I am sure.ControllerContextwas only added 2 years ago? This code predates that. Maybe that's the problem... I'll look into updating tomorrow, I guess.