快速的答案是使用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)
}
}
}
但这掩盖了为什么它可以解决问题。
在解决此问题之前,您至少应该对三件事有所了解。我必须承认我是货神教这个很长一段时间,当我开始与框架的工作。我花了相当长时间才真正了解正在发生的事情。
这三件事是:
- 怎么
LabelFor
和其他...For
助手在MVC工作?
- 什么是表情树?
- Model Binder如何工作?
所有这三个概念链接在一起以获得答案。
怎么LabelFor
和其他...For
助手在MVC工作?
所以,你已经使用了HtmlHelper<T>
用于扩展LabelFor
和TextBoxFor
与其他人,你可能已经注意到了,当你调用它们,你通过他们的λ,它奇迹般地产生一些HTML。但是如何?
因此,首先要注意的是这些助手的签名。让我们看一下最简单的重载
TextBoxFor
public static MvcHtmlString TextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
首先,这是键入的强扩展方法HtmlHelper
类型,<TModel>
。因此,为了简单说明幕后发生的事情,当剃刀渲染此视图时,它将生成一个类。该类的内部是HtmlHelper<TModel>
(作为属性Html
,这就是为什么可以使用@Html...
)的实例,其中TModel
是@model
语句中定义的类型。因此,在您的情况下,当您查看此视图时,该视图TModel
始终为ViewModels.MyViewModels.Theme
。
现在,下一个参数有些棘手。因此,让我们看一下调用
@Html.TextBoxFor(model=>model.SomeProperty);
看起来我们有点lambda,如果要猜测签名,可能会认为此参数的类型只是a Func<TModel, TProperty>
,其中TModel
视图模型的类型是TProperty
的类型是推断的,是属性的类型。
但是,如果您查看参数its 的实际类型,那是不对的Expression<Func<TModel, TProperty>>
。
因此,当您通常生成lambda时,编译器会将该lambda并将其编译为MSIL,就像其他任何函数一样(这就是为什么您可以或多或少地互换使用委托,方法组和lambda的原因,因为它们只是代码引用)
但是,当编译器看到类型为 Expression<>
,它不会立即编译拉姆达下降到MSIL,而是产生一个表达式树!
因此,到底什么是表达树。好吧,这并不复杂,但也不是在公园散步。引用ms:
| 表达式树表示树状数据结构中的代码,其中每个节点都是表达式,例如,方法调用或诸如x <y的二进制运算。
简而言之,表达式树是功能作为“动作”集合的表示。
如果是 model=>model.SomeProperty
,表达式树中将有一个节点,上面写着:“从“模型”中获取“某些属性””
该表达式树可以编译为可以调用的函数,但是只要它是表达式树,它就是节点的集合。
那有什么好处呢?
所以Func<>
或Action<>
,一旦有了它们,它们几乎是原子的。您真正能做的就是Invoke()
他们,也就是告诉他们去做他们应该做的工作。
Expression<Func<>>
另一方面,表示动作的集合,可以附加,操纵,访问或编译和调用这些动作。
那你为什么要告诉我这一切?
因此,有了对“什么Expression<>
是”的理解,我们可以回到Html.TextBoxFor
。呈现文本框时,它需要生成一些有关您为其提供的属性的信息。事情是这样attributes
的财产进行验证,特别在这种情况下,需要弄清楚如何命名的<input>
标签。
它通过“遍历”表达式树并建立名称来实现。因此,对于像这样的表达式model=>model.SomeProperty
,它将遍历该表达式,以收集您要查找的属性并进行构建<input name='SomeProperty'>
。
对于更复杂的示例,例如model=>model.Foo.Bar.Baz.FooBar
,它可能会生成<input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />
合理?在这里,不仅要做的是工作Func<>
,而且如何完成工作也很重要。
(请注意,诸如LINQ to SQL之类的其他框架通过遍历表达式树并构建不同的语法(在这种情况下为SQL查询)来执行类似的操作)
Model Binder如何工作?
因此,一旦您了解了这一点,我们就必须简要讨论一下模型绑定器。发布表单时,就像是一个flat
Dictionary<string, string>
,我们失去了嵌套视图模型可能具有的层次结构。模型绑定器的工作是采用此键值对组合并尝试为具有某些属性的对象补水。它是如何做到的?您使用发布的输入的“键”或名称来猜对了。
因此,如果表单发布看起来像
Foo.Bar.Baz.FooBar = Hello
然后,您将发布到名为的模型SomeViewModel
,那么它的作用与助手最初所做的相反。它寻找一个名为“ Foo”的属性。然后从“ Foo”中查找一个名为“ Bar”的属性,然后从“ Baz”中查找...等等。
最后,它尝试将值解析为“ FooBar”的类型,并将其分配给“ FooBar”。
EW!
瞧,您有了模型。刚构造的Model Binder实例将移至请求的Action中。
因此您的解决方案不起作用,因为Html.[Type]For()
助手需要表达。而您只是给他们一个价值。它不知道该值的上下文是什么,也不知道如何处理它。
现在有人建议使用局部视图进行渲染。现在,理论上这将起作用,但可能不会达到您期望的方式。渲染局部图像时,您将更改的类型TModel
,因为您处于不同的视图上下文中。这意味着您可以用较短的表达式描述属性。这也意味着当助手为您的表达式生成名称时,它会很浅。它只会根据给定的表达式生成(而不是整个上下文)。
因此,可以说您有一个部分仅渲染了“ Baz”(来自之前的示例)。在该部分中,您只能说:
@Html.TextBoxFor(model=>model.FooBar)
而不是
@Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
这意味着它将生成一个如下所示的输入标签:
<input name="FooBar" />
如果您将此表单发布到一个需要大型深度嵌套ViewModel的操作中,则它将尝试合并名为FooBar
off的属性TModel
。充其量是没有的,而充其量是最糟糕的。如果您要发布到接受Baz
而不是根模型的特定操作,那么效果很好!实际上,局部视图是更改视图上下文的好方法,例如,如果您的页面具有多种形式,并且所有页面都张贴到不同的动作,那么为每个页面呈现局部视图将是一个好主意。
现在,一旦您掌握了所有这些内容,就可以开始使用进行真正有趣的事情Expression<>
,方法是以编程方式扩展它们并使用它们进行其他巧妙的处理。我什么都不会做。但是,希望这可以使您更好地了解幕后发生的事情以及事物按原样运行的原因。
@
所有的前foreach
S' 您不应该在Html.EditorFor
(Html.EditorFor(m => m.Note)
例如)和其余方法中也使用lambda 吗?我可能会误会,但是您可以粘贴您的实际代码吗?我对MVC还是很陌生,但是您可以使用局部视图或编辑器(如果是名称?)来轻松解决它。