@jasonplackey
jplackey
LinkedIn
Email
Debugging a Tricky Razor Exception
Tuesday, June 04, 2019

A coworker came to me today and asked if I would take a look at some code he'd written. He was getting an exception in a Razor view, and he couldn't figure out why. After picking apart the code, it turned out to be a pretty tricky issue – one which I'm assuming is actually due to a bug in Visual Studio 2017.

He had some code which looked like this in his view:

  ... some code above here ...
  <ul>
    @foreach ( var item in Model.ItemList ) {
      <li>@item.Name</li>
    }
  </ul>
  ... some code below here ...

The code looked fine, and when executed, he was getting an Index was out of range exception on the @foreach line. Now... the first thing that may jump out at you as odd is that you shouldn't even get this exception when using foreach.

Nonetheless, we ran through some usual checks, including making sure that Model.ItemList did in fact contain some items (it did: 298 items). We then replaced the foreach loop with a for loop. Same result. We lowered the iteration count of the loop from Model.ItemList.Count() (298) to 5 and then to 1. Same result.

At this point, I started wondering if somehow the ItemList data was being modified (by another thread?) while being iterated over, but this wasn't the case from what I could tell. By now, I was starting to get a little puzzled – then something tipped me off...

When the exception was raised while running in debug mode, even though the debugger stopped on the line which now said for ( var idx = 0; idx < Model.ItemList.Count(); idx++ ), the value of idx couldn't be inspected. It was that little "hint" that made me think that perhaps the exception being reported wasn't correct.

I removed all of the code in the view above and below the loop in question, and the exception went away. Now, we just had to figure out why... Long story short, a good 50 or 60 lines below the loop, there was a line of code which said something like:

<input type="text" value="@Model.SomeOtherList[5]" />

I checked the model, and sure enough, SomeOtherList contained only three elements. I was pretty sure that this was the root of the exception. But why had Visual Studio reported it on the loop 50+ lines above this?

My initial thought was that it had to do with the @Model shorthand versus an actual code block. To test this, I replaced:

<input type="text" value="@Model.SomeOtherList[5]" />

...with...

<input type="text" value="@{ Html.Raw(Model.SomeOtherList[5]); }" />

With this change, the exception was raised on the correct line. My coworker was back in business, and life was good. I figured the whole experience would make a good blog post, but I wanted to test my theory as to why this happened a bit further, so I threw together a small test project.

Our Model

Our Controller

Our View

The Result

Just as before, the exception which actually occurred on line #5 was reported on line #3. Line #3, however, obviously has nothing wrong with it; we clearly passed in a list containing three items. To test my theory about the @Model shorthand, I tried the following:

Here, simply changing value to data-value caused the exception to be raised on the correct line (#5). Apparently, the value attribute has something to do with the problem. The following you may recall is what I tried earlier, which ended up getting the exception thrown on the correct line:

Taking the Html.Raw call out of the braces, however, yields a much different result:

You'll notice above that I swapped the order of the input and span elements from the earlier examples, and I'm assuming this is why no relevant source lines was reported – the runtime can traverse up the source but not down looking for the exception. That said, the effect of removing the span element completely was stranger still, despite the span being after the input element:

Given that the problematic behavior only seems to occur with the value attribute and only when using @Model shorthand (no braces), I'm guessing this may be pretty rare to see in the wild. I didn't attempt to dig any further into the root cause (nor do I plan to), but I'm guessing it has something to do with ASP MVC's default model binders.

Whatever the cause, it's something to be aware of, or it could have you scratching your head for a while if you're ever bitten by it...

Blog engine and all content © 2024 Jason Plackey