MVC Razor视图嵌套foreach的模型

Dav*_*d C 94 c# asp.net view razor asp.net-mvc-3

想象一个常见的场景,这是我遇到的更简单的版本.我实际上有几层进一步筑巢......

但这就是场景

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

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

订单集合有一个名为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)
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

如果我使用lambda然后我似乎只获得对顶级Model对象的引用,"Theme"不是foreach循环中的那些.

是我试图在那里做甚至可能还是我高估或误解了什么是可能的?

有了上面我在TextboxFor,EditorFor等上得到了一个错误

CS0411:无法从用法中推断出方法'System.Web.Mvc.Html.InputExtensions.TextBoxFor(System.Web.Mvc.HtmlHelper,System.Linq.Expressions.Expression>)'的类型参数.尝试显式指定类型参数.

谢谢.

J. *_*mes 303

快速回答是使用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)
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

但是这掩盖了为什么这能解决问题.

在解决此问题之前,至少有一些粗略的理解.我不得不承认,当我开始使用框架时,我很长时间没收货.我花了很长时间才真正得到了正在发生的事情.

这三件事是:

  • 如何LabelFor和其他...For助手在MVC中工作?
  • 什么是表达式树?
  • Model Binder如何工作?

所有这三个概念都联系在一起得到答案.

如何LabelFor和其他...For助手在MVC中工作?

所以,你已经使用了HtmlHelper<T>用于扩展LabelForTextBoxFor与其他人,你可能已经注意到了,当你调用它们,你通过他们的λ,它神奇地产生一些HTML.但是怎么样?

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

public static MvcHtmlString TextBoxFor<TModel, TProperty>(
    this HtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TProperty>> expression
) 
Run Code Online (Sandbox Code Playgroud)

首先,这是键入的强扩展方法HtmlHelper类型,<TModel>.因此,为了简单说明幕后发生的事情,当剃刀呈现此视图时,它会生成一个类.这个类的内部是HtmlHelper<TModel>(作为属性Html,这就是你可以使用的原因@Html...)的实例,其中TModel是你的@model语句中定义的类型.因此,在您的情况下,当您查看此视图​​时,TModel 将始终属于该类型ViewModels.MyViewModels.Theme.

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

@Html.TextBoxFor(model=>model.SomeProperty);
Run Code Online (Sandbox Code Playgroud)

看起来我们有一个小lambda,如果有人猜测签名,可能会认为这个参数的类型只是a Func<TModel, TProperty>,TModel视图模型的类型在哪里,TProperty 并被推断为属性的类型.

但那不太对,如果你看一下它的实际类型Expression<Func<TModel, TProperty>>.

因此,当您通常生成lambda时,编译器将lambda并将其编译为MSIL,就像任何其他函数一样(这就是为什么您可以或多或少地交替使用委托,方法组和lambd,因为它们只是代码引用.)

但是,当编译器发现类型为a时Expression<>,它不会立即将lambda编译为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如何工作?

所以,一旦你得到它,我们必须简要谈谈模型绑定器.当表单被发布时,它就像一个平面 Dictionary<string, string>,我们已经失去了嵌套视图模型可能具有的层次结构.使用此键值对组合并尝试使用某些属性重新水化对象是模型绑定器的工作.它是如何做到的?你猜对了,通过使用已发布的输入的"密钥"或名称.

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

Foo.Bar.Baz.FooBar = Hello
Run Code Online (Sandbox Code Playgroud)

而且你发布了一个名为的模型SomeViewModel,然后它与帮助者首先做的相反.它寻找一个名为"Foo"的属性.然后它从"Foo"中查找名为"Bar"的属性,然后查找"Baz"......依此类推......

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

唷!

瞧,你有自己的模特.刚刚构建的Model Binder实例将被移交给请求的Action.


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

现在有些人建议使用局部渲染.现在这在理论上是可行的,但可能不是你期望的方式.渲染部分时,您正在更改其类型TModel,因为您处于不同的视图上下文中.这意味着您可以使用较短的表达式来描述您的属性.它还意味着当助手为表达式生成名称时,它将变浅.它只会根据给定的表达式(而不是整个上下文)生成.

所以,假设你有一个只是渲染"Baz"的部分(来自我们之前的例子).在那部分内你可以说:

@Html.TextBoxFor(model=>model.FooBar)
Run Code Online (Sandbox Code Playgroud)

而不是

@Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
Run Code Online (Sandbox Code Playgroud)

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

<input name="FooBar" />
Run Code Online (Sandbox Code Playgroud)

其中,如果您将此表单发布到期望大型深度嵌套ViewModel的操作,那么它将尝试为一个名为FooBaroff的属性进行水合TModel.充其量不存在,最糟糕的是完全不同的东西.如果您要发布的是接受a Baz而不是根模型的特定操作,那么这将非常有用!实际上,partials是改变视图上下文的好方法,例如,如果你有一个页面有多个表单,所有表单都发布到不同的操作,那么为每个表单渲染一个部分将是一个好主意.


现在,一旦你完成所有这些,你就可以开始做一些非常有趣的事情Expression<>,通过编程扩展它们并用它们做其他整洁的事情.我不会进入任何一个.但是,希望这能让你更好地了解幕后发生的事情,以及为什么事情按照他们的方式行事.

  • 需要不止一个upvote.+3(每个解释一个),货物崇拜者+1.绝对精彩的答案! (14认同)
  • 很棒的回复.我正在尝试消化它.:)还犯了Cargo Culting罪!就像那个描述. (4认同)
  • 谢谢你的详细解答! (4认同)
  • 这就是我喜欢SO的原因:简短的回答+深入的解释+很棒的链接(货物崇拜).我想向任何不认为有关内部工作原理的知识非常重要的人展示关于货物崇拜的帖子! (3认同)

Ali*_*uri 18

您只需使用EditorTemplates来执行此操作,您需要在控制器的视图文件夹中创建一个名为"EditorTemplates"的目录,并为每个嵌套实体(命名为实体类名称)放置一个单独的视图

主要观点:

@model ViewModels.MyViewModels.Theme

@Html.LabelFor(Model.Theme.name)
@Html.EditorFor(Model.Theme.Categories)
Run Code Online (Sandbox Code Playgroud)

类别视图(/MyController/EditorTemplates/Category.cshtml):

@model ViewModels.MyViewModels.Category

@Html.LabelFor(Model.Name)
@Html.EditorFor(Model.Products)
Run Code Online (Sandbox Code Playgroud)

产品视图(/MyController/EditorTemplates/Product.cshtml):

@model ViewModels.MyViewModels.Product

@Html.LabelFor(Model.Name)
@Html.EditorFor(Model.Orders)
Run Code Online (Sandbox Code Playgroud)

等等

这样,Html.EditorFor帮助器将以有序的方式生成元素的名称,因此您将不会有任何进一步的问题来检索已发布的主题实体作为一个整体