为什么我的 razor 视图会针对错误的代码部分抛出 InvalidOperationException?

Why does my razor view throw InvalidOperationException for the wrong part of the code?

我有一个像这样循环的视图

 @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>
 }

当我访问该页面时,我收到一个 InvalidOperationException,其中包含令人烦恼的部分 "Sequence contains no elements"。

完整堆栈跟踪:

[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

我检查了从控制器返回时和进入循环时集合不为空以确保这不是问题所在。但是一旦它试图获取循环中的第一项,我就会得到异常。这不应该发生,因为它是一个 foreach 循环。

但是,在视图的下方我有这部分:

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

将其更改为:

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

修复了根本问题。

但是我的问题是,为什么在访问 foreach 循环时抛出异常然后显示不正确的堆栈跟踪,而不是在访问不存在的项目时抛出异常?

编辑: 添加更多证据,因为人们回答了错误的问题。请尝试回答实际问题。

编辑 2:

我创建了一个最小的可复制版本

控制器:

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; }
}

查看:

@model Dashboard.Controllers.TestModel



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


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

似乎出于某种原因,它在 foreach 循环之前用 First() 解析了行,但直到访问 foreach 循环才抛出异常?

这就是 "First()" 的工作原理:https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.first?view=netframework-4.8

当你使用"First"时,这个returns是列表的第一个元素,如果没有元素,这会给你一个InvalidOperationException”,因为你做了一个无效的操作。"First()" 在返回你的值之前不检查元素是否存在。

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?

不是这样的,foreach循环一执行(这里不往里走),

的下一条语句
<input type="hidden" value="@Model.TestList.First().S" />

已执行(由 Razor 引擎渲染)。

您可以通过以下代码确认。

 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; }
    }

并在 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()" />

现在在

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

您会注意到,一旦执行 foreach,下一个方法 AnyMethod 或者在您的情况下 TestList.First() 被执行并抛出异常。为什么 TestList.First() 抛出异常可以在 andrés matínez 的回答

中阅读

我能够使用提供的最少示例代码重现错误,但是 只能通过 运行 "Release" 构建配置。 当 运行 "Debug" 配置,ASP.Net 错误页面显示错误源自预期的行(包含对 First() 的调用)。

看起来混乱是由编译器优化引起的。默认情况下,为 "Release" 个构建启用优化,而不为 "Debug" 个构建启用优化。启用优化后,编译器本质上可能 rewrite code to make it more efficient。然后,代码可以在程序执行时由即时 ("JIT") 编译器再次重写。启用优化后,执行的代码可能看起来与原始源代码有很大不同——不同到无法提供带有映射回这些源代码的行号的堆栈跟踪。

就普通开发人员而言,编译器优化是巫术。猜测一段给定的代码将如何优化几乎是不可能的。如果你有足够的动力,你可以检查优化后的代码,看看有什么变化--this and this答案(都是对同一个问题的答案)给出一些如何做的建议。

不管怎样,我快速浏览了一下反编译后的视图,并没有发现任何明显奇怪的地方。这表明 JIT 编译器是罪魁祸首。 (不过,我不是编译器优化方面的专家,所以我在那里可能是错的)

无论如何...确保您使用的是 "Debug" 配置(或禁用编译器优化的其他配置),我认为问题会消失。

看来混淆是 Razor 页面的呈现顺序。在页面上呈现所有控件之前,不会呈现和评估 foreach 循环。
简而言之,Html 在 Razor 代码块被渲染之前被渲染。 请参阅此问题以简要了解该过程: What is the execution order of an MVC Razor view/layout 如上所述,异常显然出现在对 First 的 linq 调用上。我假设由于在渲染过程中发生异常,页面不完整并且可能只是这种状态的条件使得出现执行当前在foreach循环而不是控制。