-2

I have a View with a loop like this

 @foreach (var item in Model.RoutineAttachments)
 {
     <tr>
         <td>@item.Attachment.Name</td>
         <td>@item.Attachment.Weight</td>
         <td>@item.Attachment.Thickness</td>
         <td>@item.IsGeneric</td>
         <td>@Html.ActionLink("Delete", "DeleteAttachment", new { routineId = item.RoutineId, attachmentId = item.Attachment.Id }, new { @class = "btn btn-danger btn-sm" })</td>
     </tr>
 }

When I access the page I get an InvalidOperationException with the vexing part "Sequence contains no elements".

Full stack trace:

[InvalidOperationException: Sequence contains no elements]
   System.Linq.Enumerable.First(IEnumerable`1 source) +335
   ASP._Page_Views_Routine_EditAttachments_cshtml.Execute() in c:\Project\Dashboard\Views\EditAttachments.cshtml:19
   System.Web.WebPages.WebPageBase.ExecutePageHierarchy() +197
   System.Web.Mvc.WebViewPage.ExecutePageHierarchy() +105
   System.Web.WebPages.StartPage.RunPage() +17
   System.Web.WebPages.StartPage.ExecutePageHierarchy() +73
   System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) +78
   System.Web.Mvc.RazorView.RenderView(ViewContext viewContext, TextWriter writer, Object instance) +235
   System.Web.Mvc.BuildManagerCompiledView.Render(ViewContext viewContext, TextWriter writer) +107
   System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context) +291
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) +13
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilterRecursive(IList`1 filters, Int32 filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult) +56
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilterRecursive(IList`1 filters, Int32 filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult) +420
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult) +52
   System.Web.Mvc.Async.<>c__DisplayClass3_6.<BeginInvokeAction>b__4() +198
   System.Web.Mvc.Async.<>c__DisplayClass3_1.<BeginInvokeAction>b__1(IAsyncResult asyncResult) +100
   System.Web.Mvc.Async.WrappedAsyncResult`1.CallEndDelegate(IAsyncResult asyncResult) +10
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +48
   System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +27
   System.Web.Mvc.<>c.<BeginExecuteCore>b__152_1(IAsyncResult asyncResult, ExecuteCoreState innerState) +11
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +29
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +48
   System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +45
   System.Web.Mvc.<>c.<BeginExecute>b__151_2(IAsyncResult asyncResult, Controller controller) +13
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +22
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +48
   System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +26
   System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult) +10
   System.Web.Mvc.<>c.<BeginProcessRequest>b__20_1(IAsyncResult asyncResult, ProcessRequestState innerState) +28
   System.Web.Mvc.Async.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult) +29
   System.Web.Mvc.Async.WrappedAsyncResultBase`1.End() +48
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +28
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
   System.Web.CallHandlerExecutionStep.InvokeEndHandler(IAsyncResult ar) +161
   System.Web.CallHandlerExecutionStep.OnAsyncHandlerCompletion(IAsyncResult ar) +128

I checked that the collection isn't null when returning from the controller and when stepping into the loop to be sure that wasn't the issue. But as soon as it tries to get the first item in the loop, I get the exception. Which shouldn't happen since it's a foreach loop.

However, further down in the view I have this part:

<input type="hidden" value=@Model.RoutineAttachments.First().RoutineId />

Changing it to:

<input type="hidden" value=@Model.RoutineAttachments.FirstOrDefault()?.RoutineId />

Fixes the underlying issue.

My question however is, why the exception gets thrown when accessing the foreach loop and then displays an incorrect stacktrace, instead of throwing when accessing the non existing item?

EDIT: Adding more proof since people are answering the wrong question. Please try to answer the actual question.

Stacktrace showing foreach loop

Exception when stepping into foreach loop

EDIT 2:

I have created a minimal reproducible version

Controller:

public class TestController : Controller
{
    // GET: Test
    public ActionResult Index()
    {
        TestModel t = new TestModel();
        t.TestList = new List<TestItem>();
        return View(t);
    }
}

public class TestModel
{
    public List<TestItem> TestList { get; set; }
}

public class TestItem
{
    public string S { get; set; }
}

View:

@model Dashboard.Controllers.TestModel



@foreach (var item in Model.TestList)
{
    <h5>@item.S</h5>
}


<input type="hidden" value="@Model.TestList.First().S" />

It's seems that for some reason it parses the line with First() before the foreach loop, but doesn't throw the exception until accessing the foreach loop?

John
  • 192
  • 3
  • 12

4 Answers4

2

I was able to reproduce the error using the minimum sample code provided, but only by running the "Release" build configuration. When running the "Debug" configuration, the ASP.Net error page showed that the error originated on the expected line (the one containing the call to First()).

It looks like the confusion is due to compiler optimizations. By default, optimizations are enabled for "Release" builds, and not for "Debug" builds. When the optimizations are enabled, the compiler may, essentially, rewrite code to make it more efficient. The code can then be rewritten again by a just-in-time ("JIT") compiler as the program executes. When optimizations are enabled, the code that executes may look significantly different from the original sources--different enough that it's impossible to give a stack trace with line numbers that map back to those sources.

Compiler optimizations are, as far as the average developer is concerned, voodoo. It's all but impossible to guess how a given piece of code will be optimized. If you're motivated enough, you can inspect the optimized code to see what's changed--this and this answer (both are answers to the same question) give some advice on how.

For what it's worth, I took a quick look at the decompiled view, and didn't spot anything obviously weird. That suggests that the JIT compiler is the culprit. (I'm not an expert at compiler optimizations, though, so I could be wrong there)

Anyway... make sure you're using the "Debug" configuration (or another configuration with compiler optimizations disabled), and I think the problem will go away.

xander
  • 1,689
  • 10
  • 18
2

It appears the confusion is the render order of the Razor page. The foreach loop is not going to be rendered and evaluated until all of the controls are rendered on the page.
In short, Html is rendered before Razor code blocks are rendered. See this question for a brief, good explanation of the process: What is the execution order of an MVC Razor view/layout The exception is clearly on the linq call to First, as explained above. I would suppose that since the exception hits during rendering, the page is incomplete and it may simply be a condition of this state that makes it appear that the execution is currently on the foreach loop instead of the control.

Brinky
  • 418
  • 3
  • 9
0

It's seems that for some reason it parses the line with First() before the foreach loop, but doesn't throw the exception until accessing the foreach loop?

It is not the case, as soon foreach loop is executed (In this case not going inside), the next statement of

<input type="hidden" value="@Model.TestList.First().S" />

is executed (rendered by Razor engine).

you can confirm it by following code.

 public class TestController : Controller
    {
        // GET: Test
        public ActionResult Index()
        {
            TestModel t = new TestModel();
            t.TestList = new List<TestItem>();
            return View(t);
        }
    }

    public class TestModel
    {
        public List<TestItem> TestList { get; set; }

        public string AnyMethod()
        {
            return "";
        }
    }

    public class TestItem
    {
        public string S { get; set; }
    }

and in Index.cshtml

  @model Dashboard.Controllers.TestModel

@{

    var testObject = new Dashboard.Controllers.TestModel();
}


@foreach (var item in Model.TestList)
{
    <h5>@item.S</h5>
}


<input type="hidden" value="@testObject.AnyMethod()" />

Now put a breakpoint on

public string AnyMethod()
 {
   return "";
   // You may throw InvalidOperationException to see the stack trace
 }

you will notice, as soon the foreach is executed, the next method AnyMethod or in your case TestList.First() is executed that throws the exception. Why TestList.First() throws exception can be read in andrés matínez's answer

Sayyed Dawood
  • 607
  • 7
  • 15
-1

This is how "First()" Works: https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.first?view=netframework-4.8

When you use "First", this returns the first element of the list, and if there is no element, this will give you an InvalidOperationException" because you did a invalid operation. "First()" does not check if the element exists or not before returning your value.

Sayyed Dawood
  • 607
  • 7
  • 15
  • Ofcourse. That wasn't my question. My question is why im getting that exception when accessing the foreach loop – John Jan 30 '20 at 13:28
  • 2
    @John the answer provide is correct. Your exception is here System.Linq.Enumerable.First(IEnumerable`1 source) +335 second line down. It is not triggered by the foreach loop. – Alex Leo Feb 01 '20 at 12:06
  • The stack trace points to the row of the foreach loop and debugging the razor view pops the exception when stepping in to the loop. – John Feb 02 '20 at 14:43
  • @AlexLeo I have updated with a picture to show you that it is indeed the foreach loop that is triggering it. – John Feb 03 '20 at 07:57
  • Yeah that is correct - as that is indeed the case, please look up at this [post](https://stackoverflow.com/questions/3088147/why-does-net-foreach-loop-throw-nullrefexception-when-collection-is-null) - it explains how the compiler handles a null collection and an empty one. – Alex Leo Feb 03 '20 at 08:42
  • 1
    No. That would throw a NullReferenceException if the collection was null. The collection is empty and not null, it should therefore just continue past the foreach loop and then throw the InvalidOperationException when accessing First() further down. Now it throws InvalidOperationException and not a NullReferenceException when iterating the loop, as you can see in the gif – John Feb 03 '20 at 09:06