想象一个常见的场景,这是我遇到的一个更简单的版本.实际上,我的身上还有几层更深的巢穴……

但情况就是这样

主题包含列表 类别包含列表 产品包含列表

我的控制器提供了一个完全填充的主题,包含该主题的所有类别、该类别中的产品及其订单.

Orders集合有一个名为Quantity的属性(以及许多其他属性),该属性需要是可编辑的.

@model ViewModels.MyViewModels.Theme

@Html.LabelFor(Model.Theme.name)
@foreach (var category in Model.Theme)
{
   @Html.LabelFor(category.name)
   @foreach(var product in theme.Products)
   {
      @Html.LabelFor(product.name)
      @foreach(var order in product.Orders)
      {
          @Html.TextBoxFor(order.Quantity)
          @Html.TextAreaFor(order.Note)
          @Html.EditorFor(order.DateRequestedDeliveryFor)
      }
   }
}

如果我使用lambda,那么我似乎只得到了对顶层模型对象"Theme"的引用,而不是foreach循环中的引用.

我试图在那里做的事情是否可能,或者我是否高估或误解了可能发生的事情?

由于以上原因,我在TextboxFor、EditorFor等上出现了一个错误

CS0411: The type arguments for method 'System.Web.Mvc.Html.InputExtensions.TextBoxFor(System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

谢谢

推荐答案

快速的答案是用for()个循环代替foreach()个循环.比如:

@for(var themeIndex = 0; themeIndex < Model.Theme.Count(); themeIndex++)
{
   @Html.LabelFor(model => model.Theme[themeIndex])

   @for(var productIndex=0; productIndex < Model.Theme[themeIndex].Products.Count(); productIndex++)
   {
      @Html.LabelFor(model=>model.Theme[themeIndex].Products[productIndex].name)
      @for(var orderIndex=0; orderIndex < Model.Theme[themeIndex].Products[productIndex].Orders; orderIndex++)
      {
          @Html.TextBoxFor(model => model.Theme[themeIndex].Products[productIndex].Orders[orderIndex].Quantity)
          @Html.TextAreaFor(model => model.Theme[themeIndex].Products[productIndex].Orders[orderIndex].Note)
          @Html.EditorFor(model => model.Theme[themeIndex].Products[productIndex].Orders[orderIndex].DateRequestedDeliveryFor)
      }
   }
}

但是这个涂抹了why多个就解决了这个问题.

在解决这个问题之前,您至少要对三件事有一个粗略的了解.我有过 承认我有cargo-culted个这样的人 在我开始使用这个框架的很长一段时间里.我花了很长时间 才能真正了解到底发生了什么.

这三件事是:

  • LabelFor和其他...For个帮助器在MVC中是如何工作的?
  • 什么是表达式树?
  • 模型活页夹是如何工作的?

这三个概念结合在一起就可以得到答案.

LabelFor和其他...For个帮助器在MVC中是如何工作的?

所以,您已经使用了LabelForTextBoxFor等的HtmlHelper<T>个扩展,以及

所以首先要注意的是这些帮助者的签名.让我们看一下最简单的重载 TextBoxFor

public static MvcHtmlString TextBoxFor<TModel, TProperty>(
    this HtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TProperty>> expression
) 

首先,这是<TModel>类型的强类型HtmlHelper的扩展方法.所以,简单地说

现在,下一个论点有点棘手.让我们来看一个调用

@Html.TextBoxFor(model=>model.SomeProperty);

看起来我们有一个小lambda,如果你猜到了签名,你可能会认为

但这并不完全正确,如果你看一下actual型的论点,它是Expression<Func<TModel, TProperty>>.

因此,当您正常生成lambda时,编译器将获取lambda并将其向下编译为MSIL,就像其他任何文件一样 函数(这就是为什么您可以或多或少地互换使用委托、方法组和lambdas,因为它们只是 代码引用.)

但是,当编译器看到类型为Expression<>时,它不会立即将lambda编译为MSIL,而是生成一个

What is an Expression Tree?

那么,表达式树到底是什么呢.嗯,这并不复杂,但也不是在公园里散步.引用ms的话:

|表达式树表示树型数据 struct 中的代码,其中每个 node 都是一个表达式,例如,方法调用或二进制操作,如x<;Y

简单地说,表达式树是函数作为"动作"集合的表示.

对于model=>model.SomeProperty,表达式树中会有一个 node ,上面写着:"从‘模型’中获取‘某些属性’"

这个表达式树可以compiled变成一个可以调用的函数,但是只要它是一个表达式树,它就只是一个 node 的集合.

那么这有什么好处呢?

所以Func<>Action<>,一旦你有了它们,它们基本上是原子的.你能做的就是告诉他们

另一方面,Expression<Func<>>表示动作的集合,可以附加、操纵、visitedcompiled并调用这些动作.

那你为什么要告诉我这些?

所以理解了Expression<>是什么之后,我们就可以回到Html.TextBoxFor了.当它呈现文本框时,它需要 来产生一些东西about你给它的属性.属性上用于验证的内容(如attributes),特别是 在这种情况下,它需要找出如何对<input>标签进行name.

它通过"遍历"表达式树并建立一个名称来实现这一点.所以对于像model=>model.SomeProperty这样的表达式,它会遍历表达式

对于一个更复杂的例子,比如model=>model.Foo.Bar.Baz.FooBar,它可能会生成<input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />

有道理?这不仅仅是Func<>所做的工作,而且how它所做的工作在这里很重要.

(注意,像LINQ到SQL这样的其他框架通过遍历表达式树并构建不同的语法(本例中是SQL查询)来做类似的事情)

模型活页夹是如何工作的?

一旦你明白了这一点,我们就得简单地谈谈模型活页夹了.当表格被张贴时,它简直就像一个公寓 Dictionary<string, string>,我们就失go 了嵌套视图模型可能具有的层次 struct .这是 模型绑定器的工作是获取这个键-值对组合,并try 恢复具有某些属性的对象.它是怎么做的? 这?您可以通过使用"键"或发布的输入的名称来猜到.

所以如果表单帖子看起来像

Foo.Bar.Baz.FooBar = Hello

你在一个名为SomeViewModel的模型上发帖,然后它会做与助手最初做的相反的事情.它寻找

最后,它try 将值解析为"FooBar"类型,并将其分配给"FooBar".

PHEW!!!

瞧,你有你的模特了.模型绑定器刚刚构建的实例被提交到请求的操作中.


所以您的解决方案不起作用,因为这Html.[Type]For()个帮助器需要一个表达式.而你只是给了他们一个价值.它根本不知道 该值的上下文是什么,它不知道如何处理它.

现在有人建议使用partials来渲染.这在理论上是可行的,但可能不是你期望的那样.渲染局部时,您正在更改TModel的类型,因为您处于不同的视图上下文中.这意味着你可以描述

假设你有一个片段,刚刚呈现了"Baz"(来self 们之前的例子).在这一部分中,你可以说:

@Html.TextBoxFor(model=>model.FooBar)

而不是

@Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)

这意味着它将生成如下所示的输入标记:

<input name="FooBar" />

如果您将此表单发布到一个需要一个大型深度嵌套ViewModel的操作,那么它将try 创建一个属性


现在,一旦你掌握了所有这些,你就可以开始用Expression<>来做真正有趣的事情,通过编程来扩展它们,然后

Asp.net相关问答推荐

如何处理当前文件中基本文件中的S onClick方法

如何在 ASP.Net Core 中验证上传的文件

我们可以在一个网页中使用多个表单吗?

*不*使用 asp.net 会员提供程序是个坏主意吗?

ASP.NET @Register 与 @Reference

Azure Service Fabric 是否与 Docker 做同样的事情?

更新到 ASP.NET Core 2.0 - 包与 netcoreapp2.0 不兼容

使用 ASP.NET WebForms 的 jQuery DataTables 服务器端处理

嵌套剃刀模板中的@RenderSection

尽管安装了 AspNetCoreModule,但在 IIS 中运行 ASP.NET Core 应用程序时出现 0x8007000d 错误 500.19

如何从 SQL Server 2008 本身获取客户端 IP 地址?

asp.net 有控制台日志(log)吗?

ASP.NET 元:资源键

捕获的异常本身为 null !

调查 Web.Config 重复部分原因的步骤

哪个控件导致了回发?

System.Linq.IQueryable 不包含Where的定义

为什么 Controls 集合不提供所有 IEnumerable 方法?

在控制器 asp.net-core 中获取当前区域性

Visual Studio 2013 ASP.NET 项目中 Antlr 包的用途是什么?