快速的答案是使用
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) } }}但这掩盖了 为什么 它可以解决问题。
在解决此问题之前,您至少应该对三件事有所了解。我不得不承认,我货物culted这个很长一段时间,当我开始与框架的工作。我花了相当长时间才真正了解正在发生的事情。
这三件事是:
- 如何在
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)
首先,这是
HtmlHelpertype
的强类型的扩展方法
<TModel>。因此,为了简单说明幕后发生的事情,当剃刀渲染此视图时,它将生成一个类。该类的内部是
HtmlHelper<TModel>(作为属性
Html,这就是为什么可以使用
@Html...)的实例,其中
TModel是
@model语句中定义的类型。因此,在您的情况下,当您查看此视图时,
TModel
将始终为类型
ViewModels.MyViewModels.Theme。
现在,下一个参数有些棘手。因此,让我们看一下调用
@Html.TextBoxFor(model=>model.SomeProperty);
似乎我们有点lambda,如果要猜测签名,则可能会认为此参数的类型只是a
Func<TModel,TProperty>,其中
TModelview模型
TProperty的类型被推断为属性的类型。
但这并不完全正确,如果您查看参数的 实际 类型
expression<Func<TModel, TProperty>>。
因此,当您通常生成lambda时,编译器会将该lambda并将其编译为MSIL,就像其他任何函数一样(这就是为什么您可以或多或少可互换地使用委托,方法组和lambda的原因,因为它们只是代码引用)
)
但是,当编译器看到类型为时
expression<>,它不会立即将lambda编译为MSIL,而是生成一个expression Tree!
什么是表情树?
因此,到底是一个表达式树。好吧,这并不复杂,但也不是在公园散步。引用ms:
| 表达式树表示树状数据结构中的代码,其中每个节点都是表达式,例如,方法调用或诸如x <y的二进制运算。
简而言之,表达式树是功能作为“动作”集合的表示。
在的情况下
model=>model.SomeProperty,表达式树中将有一个节点,上面写着:“从“模型”中获取“某些属性””
可以将表达式树 编译 成可以调用的函数,但是只要它是表达式树,它就是节点的集合。
那有什么好处呢?
所以
Func<>或
Action<>,一旦有了它们,它们几乎是原子的。您真正能做的就是
Invoke()他们,也就是告诉他们去做他们应该做的工作。
expression<Func<>>另一方面,表示动作的集合,可以将其附加,操纵,访问或编译和调用。
那你为什么要告诉我这一切?
因此,有了对“什么
expression<>是”的理解,我们可以回到
Html.TextBoxFor。呈现文本框时,它需要生成一些 有关
您为其提供的属性的信息。事情是这样
attributes的财产进行验证,特别在这种情况下,需要弄清楚如何 命名 的
<input>标签。
它通过“遍历”表达式树并建立名称来实现。因此,对于像这样的表达式
model=>model.SomeProperty,它将遍历该表达式,以收集您要的属性并进行构建
<inputname='SomeProperty'>。
对于更复杂的示例,例如
model=>model.Foo.Bar.Baz.FooBar,它可能会生成
<inputname="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的动作中,则它将尝试合并名为
FooBaroff的属性
TModel。充其量是没有的,而充其量是最糟糕的。如果您要发布到接受
Baz而不是根模型的特定操作,那么效果很好!实际上,局部视图是更改视图上下文的好方法,例如,如果您的页面具有多种形式,并且所有页面都发布到不同的操作,那么为每个页面呈现局部视图将是一个好主意。
现在,一旦您掌握了所有这些内容,就可以开始使用进行真正有趣的事情
expression<>,方法是对它们进行编程扩展,并使用它们进行其他巧妙的工作。我什么都不会做。但是,希望这可以使您更好地了解幕后发生的事情以及事物按原样运行的原因。



