MVC Razor视图嵌套的foreach的模型

设想一个常见的情况,这是我遇到的更简单的版本。 实际上我有几层在我的进一步嵌套….

但是这是情况

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

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

订单集合中有一个名为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而不是那么然后我似乎只得到顶级模型对象的引用,“主题”不在那些在foreach循环。

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

与上述我得到一个TextboxFor,EditorFor等错误

CS0411:方法“System.Web.Mvc.Html.InputExtensions.TextBoxFor(System.Web.Mvc.HtmlHelper,System.Linq.Expressions.Expression>)”的types参数不能从用法中推断出来。 尝试明确指定types参数。

谢谢。

快速的答案是使用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) } } } 

但是这掩盖了为什么这解决了这个问题。

在解决这个问题之前,有三件事至less有一个粗略的理解。 我不得不承认,当我开始使用这个框架的时候,我已经有很长一段时间了。 我花了相当长的时间才真正了解发生了什么事情。

这三件事是:

  • LabelFor和其他...For如何帮助者在MVC中工作?
  • 什么是expression式树?
  • 型号粘结剂如何工作?

所有这三个概念链接在一起得到答案。

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

所以,你已经使用了LabelForTextBoxForHtmlHelper<T>扩展,你可能会注意到,当你调用它们时,你传递给它一个lambda,它神奇地生成一些html。 但是,如何?

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

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

首先,这是一个types为<TModel>的强typesHtmlHelper的扩展方法。 所以,为了简单说明在幕后发生的事情,当剃刀渲染这个视图时,它会生成一个类。 这个类的内部是一个HtmlHelper<TModel> (作为属性Html ,这就是为什么你可以使用@Html... )的@Html... ,其中TModel@model语句中定义的types。 所以在你的情况下,当你正在查看这个视图时, TModel将始终为ViewModels.MyViewModels.Themetypes。

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

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

它看起来像我们有一个lambda,如果有人猜测签名,可能会认为这个参数的types只是一个Func<TModel, TProperty> ,其中TModel是视图模型的types, TProperty是推断为财产的types。

但是这不太正确,如果你看看它的Expression<Func<TModel, TProperty>>实际types。

所以当你通常生成一个lambdaexpression式时,编译器将lambdaexpression式编译成MSIL,就像任何其他函数一样(这就是为什么你可以或多或less地使用委托,方法组和lambdaexpression式,因为它们只是代码引用。)

但是,当编译器发现types是Expression<> ,它不会立即将lambda编译到MSIL,而是生成一个expression式树!

什么是expression式树 ?

那么,什么是expression树。 那么,这并不复杂,但它也不是在公园散步。 引用ms:

| expression式树表示树状数据结构中的代码,其中每个节点是expression式,例如方法调用或诸如x <y的二进制操作。

简而言之,expression式树是一个函数的表示forms,作为“动作”的集合。

model=>model.SomeProperty的情况下,expression式树中将会有一个节点,它表示:“从'model'中获取'一些属性'

这个expression式树可以被编译成一个可以被调用的函数,但是只要它是一个expression式树,它只是一个节点的集合。

那么有什么好处?

所以Func<>或者Action<> ,一旦你拥有它们,它们几乎是primefaces的。 你所能做的只是Invoke()它们,也就是告诉他们做他们应该做的工作。

另一方面, Expression<Func<>>表示可以附加,操作, 访问或编译和调用的动作集合。

那么你为什么告诉我这一切?

因此,通过对Expression<>理解,我们可以回到Html.TextBoxFor 。 当它呈现一个文本框时,它需要产生一些关于你给它的属性的东西。 像attributes上的属性进行validation的东西,特别是在这种情况下,它需要找出什么来命名 <input>标记。

它通过“行走”expression树和build立一个名字来做到这一点。 因此,对于像model=>model.SomeProperty这样的expression式,它会model=>model.SomePropertyexpression式收集您要求的属性并构build<input name='SomeProperty'>

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

合理? 这不仅仅是Func<>所做的工作,而且它的工作在这里是非常重要的。

(注意LINQ to SQL等其他框架通过遍历expression式树和构build不同的语法来做类似的事情,这种情况是SQL查询)

型号粘结剂如何工作?

所以,一旦你明白了,我们不得不简单地谈论模型活页夹。 当表单发布时,它就像一个扁平的Dictionary<string, string> ,我们已经失去了嵌套视图模型可能具有的层次结构。 这是模型联编程序的工作,采取这个键值对组合,并尝试用一些属性重新水化一个对象。 这是怎么做到的? 你猜对了,通过使用“键”或input的名字张贴。

所以如果表单文章看起来像

 Foo.Bar.Baz.FooBar = Hello 

而且你正在发布一个名为SomeViewModel的模型,那么它就完成了帮手首先做的事情。 它寻找一个名为“Foo”的属性。 然后从“Foo”中寻找名为“Bar”的属性,然后查找“Baz”…等等…

最后它试图将这个值parsing成“FooBar”的types并赋值给“FooBar”。

唷!

瞧,你有你的模型。 模型绑定器刚刚构build的实例被传递到请求的Action中。


所以你的解决scheme不起作用,因为Html.[Type]For()助手需要一个expression式。 而你只是给他们一个价值。 它不知道这个价值的背景是什么,它不知道如何处理它。

现在有人build议使用partials来渲染。 现在这理论上可行,但可能不是你所期望的。 当你渲染一个部分时,你正在改变TModel的types,因为你在不同的视图上下文中。 这意味着您可以用较短的expression式来描述您的财产。 这也意味着当助手为你的expression生成名字的时候,它会变浅。 它只会根据给出的expression式(而不是整个上下文)生成。

所以可以说,你有一个刚刚呈现“巴兹”(从我们的例子之前)的部分。 在这部分内部,你可以说:

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

而不是

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

这意味着它会生成一个如下所示的input标签:

 <input name="FooBar" /> 

其中,如果您将此表单发布到期望深度嵌套的ViewModel的操作,那么它将试图从TModel水合名为FooBar的属性。 最好不在那里,最坏的情况是完全不同的。 如果您发布的是接受Baz的特定操作,而不是根模型,那么这将非常棒! 事实上,partials是改变你的视图上下文的一个好方法,例如,如果你有一个页面有多个表单,所有表单都发布到不同的动作,那么为每个表单渲染一个部分将是一个好主意。


现在一旦你掌握了所有这一切,就可以用Expression<>开始做一些有趣的事情,通过编程来扩展它们,并且用它们做其他的整齐的事情。 我不会进入任何的。 但是,希望这会让你更好地理解幕后发生的事情,以及为什么事情按照自己的方式行事。

您可以简单地使用EditorTemplates来做到这一点,您需要在控制器的视图文件夹中创build一个名为“EditorTemplates”的目录,并为每个嵌套实体(命名为实体类名称)分别放置一个视图,

主要观点:

 @model ViewModels.MyViewModels.Theme @Html.LabelFor(Model.Theme.name) @Html.EditorFor(Model.Theme.Categories) 

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

 @model ViewModels.MyViewModels.Category @Html.LabelFor(Model.Name) @Html.EditorFor(Model.Products) 

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

 @model ViewModels.MyViewModels.Product @Html.LabelFor(Model.Name) @Html.EditorFor(Model.Orders) 

等等

这样Html.EditorFor helper将以有序的方式生成元素的名字,因此你不会有任何进一步的问题来检索整个发布的主题实体

你可以添加一个Category partial和一个Product partial,每个模型都会占据主模型的一小部分,就像它自己的模型一样,也就是说Category的模型types可能是一个IEnumerable,你可以通过Model.Theme传递给它。 该产品的部分可能是一个IEnumerable,您将Model.Products传递到(从部分部分中)。

我不确定这是否是正确的方向,但有兴趣知道。

编辑

自发布这个答案,我已经使用EditorTemplates,并find这个处理重复input组或项目的最简单的方法。 它处理所有的validation消息问题,并自动形成提交/模型绑定困境。

当你在绑定模型的视图中使用foreach循环…你的模型应该是列出的格式。

 @model IEnumerable<ViewModels.MyViewModels> @{ if (Model.Count() > 0) { @Html.DisplayFor(modelItem => Model.Theme.FirstOrDefault().name) @foreach (var theme in Model.Theme) { @Html.DisplayFor(modelItem => theme.name) @foreach(var product in theme.Products) { @Html.DisplayFor(modelItem => product.name) @foreach(var order in product.Orders) { @Html.TextBoxFor(modelItem => order.Quantity) @Html.TextAreaFor(modelItem => order.Note) @Html.EditorFor(modelItem => order.DateRequestedDeliveryFor) } } } }else{ <span>No Theam avaiable</span> } } 

从错误中可以明显看出。

附加“For”的HtmlHelpers需要lambdaexpression式作为参数。

如果直接传递值,最好使用Normal。

例如

而不是TextboxFor(….)使用Textbox()

TextboxFor的语法将像Html.TextBoxFor(m => m.Property)

在你的场景中,你可以使用基本的for循环,因为它会给你索引使用。

 @for(int i=0;i<Model.Theme.Count;i++) { @Html.LabelFor(m=>m.Theme[i].name) @for(int j=0;j<Model.Theme[i].Products.Count;j++) ) { @Html.LabelFor(m=>m.Theme[i].Products[j].name) @for(int k=0;k<Model.Theme[i].Products[j].Orders.Count;k++) { @Html.TextBoxFor(m=>Model.Theme[i].Products[j].Orders[k].Quantity) @Html.TextAreaFor(m=>Model.Theme[i].Products[j].Orders[k].Note) @Html.EditorFor(m=>Model.Theme[i].Products[j].Orders[k].DateRequestedDeliveryFor) } } }