I'm running into a really weird case in a cshtml Razor Page where regular code in @{ ... }
blocks can access and use a variable just fine, but attempting to access the same variables but in a @(...)
context throws NullReferenceException
s.
To debug the issue, I moved the model property navigation and access to a local variable that is definitely non-null and then try to print that with a @(..)
block, but the NullReferenceException
persists.
@foreach (var sibling in Model.SiblingApplications)
{
string studentName = sibling.Student.ToString() ?? "";
<div class="sibling half line">
<div class="question-block grow">
<div class="label">Name</div>
@* NullReferenceException on the next line! *@
<div class="value">@studentName</div>
</div>
</div>
}
Originally, the NullReferenceException
came from writing@(sibling.Student)
directly, even though I added assertions above it to throw if any of the variables used by the property access or the ToString()
override where null
. Then I refactored the code to what you see above, storing the variable in a non-null string (and doubly ensuring that's the case by appending ?? ""
out of paranoia).
Despite all that the NullReferenceException
remains. I'm unable to observe the issue locally in either of Debug or Release modes but it happens without fail and gets logged in-prod. I attempted to downgrade from .NET 7 to .NET 6 and tried deploying a Debug build instead of Release, but neither of those helped.
The stack trace isn't helpful; it just points to the line that's annotated with NullReferenceException
above:
System.NullReferenceException: Object reference not set to an instance of an object.
at ProjectName.Pages.Forms.Pages_Forms_RegistrationForm.ExecuteAsync() in X:\ProjectPath\Pages\Forms\RegistrationForm.cshtml:line 240
...
Importantly, no exception is thrown on the code that actually access the model properties and generates the non-null string - again, the exception happens accessing this now-local, definitely-not-null variable.
I couldn't figure this out without attaching a debugger, after which it immediately became clear. (Figuring out how to attach a debugger in-prod was a different matter.)
While the exception was indeed being thrown on the annotated line, in the debugger the variable that caused the NullReferenceException
is shown though it isn't named in the actual exception message or stack trace that are logged.
It turns out that (due to the optimizations?) the compiler was dereferencing two different values in one go, @sibling.Student
(used on the same line) and @sibling.AnotherProperty
(used a few lines later in the same for loop, not shown in the abbreviated example posted above).
It was the second access that was actually causing the NullReferenceException
, although the stack trace and the debugger (even attached in Debug mode) both threw/broke at the end of the first line.
I have wondered before if it is possible to get the logged exceptions and stack traces to actually include the same information that the debugger emits (namely, the property or variable that was being dereferenced when the NullReferenceException
occurred instead of just the line number) - that would have saved me (and you!) a lot of grief.