快速的答案是用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中是如何工作的?
所以,您已经使用了LabelFor
和TextBoxFor
等的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,而是生成一个
那么,表达式树到底是什么呢.嗯,这并不复杂,但也不是在公园里散步.引用ms的话:
|表达式树表示树型数据 struct 中的代码,其中每个 node 都是一个表达式,例如,方法调用或二进制操作,如x<;Y
简单地说,表达式树是函数作为"动作"集合的表示.
对于model=>model.SomeProperty
,表达式树中会有一个 node ,上面写着:"从‘模型’中获取‘某些属性’"
这个表达式树可以compiled变成一个可以调用的函数,但是只要它是一个表达式树,它就只是一个 node 的集合.
那么这有什么好处呢?
所以Func<>
或Action<>
,一旦你有了它们,它们基本上是原子的.你能做的就是告诉他们
另一方面,Expression<Func<>>
表示动作的集合,可以附加、操纵、visited或compiled并调用这些动作.
那你为什么要告诉我这些?
所以理解了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<>
来做真正有趣的事情,通过编程来扩展它们,然后