C# MVC Razor 视图嵌套 foreach 的模型
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/8894442/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
MVC Razor view nested foreach's model
提问by David C
Imagine a common scenario, this is a simpler version of what I'm coming across. I actually have a couple of layers of further nesting on mine....
想象一个常见的场景,这是我遇到的一个更简单的版本。我实际上有几层进一步嵌套在我的......
But this is the scenario
但这是场景
Theme contains List Category contains List Product contains List
主题包含列表 类别包含列表 产品包含列表
My Controller provides a fully populated Theme, with all the Categories for that theme, the Products within this categories and the their orders.
My Controller 提供了一个完全填充的主题,包含该主题的所有类别、该类别中的产品及其订单。
The orders collection has a property called Quantity (amongst many others) that needs to be editable.
订单集合有一个名为 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)
}
}
}
If I use lambda instead then then I only seem to get a reference to the top Model object, "Theme" not those within the foreach loop.
如果我改用 lambda,那么我似乎只能获得对顶级模型对象的引用,“主题”而不是 foreach 循环中的那些。
Is what I'm trying to do there even possible or have I overestimated or misunderstood what is possible?
我在那里尝试做的事情是可能的,还是我高估或误解了可能的事情?
With the above I get an error on the TextboxFor, EditorFor, etc
有了上面的内容,我在 TextboxFor、EditorFor 等上遇到了错误
CS0411: The type arguments for method 'System.Web.Mvc.Html.InputExtensions.TextBoxFor(System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
CS0411:无法从用法推断方法“System.Web.Mvc.Html.InputExtensions.TextBoxFor(System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>)”的类型参数。尝试明确指定类型参数。
Thanks.
谢谢。
采纳答案by J. Holmes
The quick answer is to use a for()loop in place of your foreach()loops. Something like:
快速答案是使用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)
}
}
}
But this glosses over whythis fixes the problem.
但这掩盖了为什么这可以解决问题。
There are three things that you have at least a cursory understanding before you can resolve this issue. I have to admit that I cargo-cultedthis for a long time when I started working with the framework. And it took me quite a while to really get what was going on.
在解决此问题之前,您至少需要粗略了解三件事。我不得不承认,当我开始使用这个框架时,我对这个很感兴趣。我花了很长时间才真正了解正在发生的事情。
Those three things are:
这三件事是:
- How do the
LabelForand other...Forhelpers work in MVC? - What is an Expression Tree?
- How does the Model Binder work?
- MVC 中的助手
LabelFor和其他...For助手如何工作? - 什么是表达式树?
- 模型绑定器如何工作?
All three of these concepts link together to get an answer.
所有这三个概念都链接在一起以获得答案。
How do the LabelForand other ...Forhelpers work in MVC?
MVC 中的助手LabelFor和其他...For助手如何工作?
So, you've used the HtmlHelper<T>extensions for LabelForand TextBoxForand others, and
you probably noticed that when you invoke them, you pass them a lambda and it magicallygenerates
some html. But how?
因此,您已经使用了and和其他的HtmlHelper<T>扩展,并且您可能注意到,当您调用它们时,您向它们传递了一个 lambda,它神奇地生成了一些 html。但是如何?LabelForTextBoxFor
So the first thing to notice is the signature for these helpers. Lets look at the simplest overload for
TextBoxFor
所以首先要注意的是这些助手的签名。让我们看一下最简单的重载
TextBoxFor
public static MvcHtmlString TextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
First, this is an extension method for a strongly typed HtmlHelper, of type <TModel>. So, to simply
state what happens behind the scenes, when razor renders this view it generates a class.
Inside of this class is an instance of HtmlHelper<TModel>(as the property Html, which is why you can use @Html...),
where TModelis the type defined in your @modelstatement. So in your case, when you are looking at this view TModelwill always be of the type ViewModels.MyViewModels.Theme.
首先,这是HtmlHelpertype的强类型的扩展方法<TModel>。因此,简单地说明幕后发生的事情,当 razor 渲染此视图时,它会生成一个类。这个类的内部是一个实例HtmlHelper<TModel>(作为属性Html,这就是为什么你可以使用@Html...),TModel你的@model语句中定义的类型在哪里。因此,在您的情况下,当您查看此视图时,该视图TModel将始终为ViewModels.MyViewModels.Theme.
Now, the next argument is a bit tricky. So lets look at an invocation
现在,下一个论点有点棘手。所以让我们看一个调用
@Html.TextBoxFor(model=>model.SomeProperty);
It looks like we have a little lambda, And if one were to guess the signature, one might think that the type for
this argument would simply be a Func<TModel, TProperty>, where TModelis the type of the view model and TPropertyis inferred as the type of the property.
看起来我们有一个小 lambda,如果要猜测签名,人们可能会认为这个参数的类型只是 a Func<TModel, TProperty>,其中TModel是视图模型TProperty的类型,并被推断为属性的类型。
But thats not quite right, if you look at the actualtype of the argument its Expression<Func<TModel, TProperty>>.
但这并不完全正确,如果您查看参数的实际类型,其Expression<Func<TModel, TProperty>>.
So when you normally generate a lambda, the compiler takes the lambda and compiles it down into MSIL, just like any other function (which is why you can use delegates, method groups, and lambdas more or less interchangeably, because they are just code references.)
因此,当您通常生成 lambda 时,编译器会使用 lambda 并将其编译为 MSIL,就像任何其他函数一样(这就是为什么您可以或多或少地互换使用委托、方法组和 lambda,因为它们只是代码引用.)
However, when the compiler sees that the type is an Expression<>, it doesn't immediately compile the lambda down to MSIL, instead it generates an
Expression Tree!
但是,当编译器发现类型是 an 时Expression<>,它不会立即将 lambda 编译为 MSIL,而是生成一个表达式树!
What is an Expression Tree?
什么是表达式树?
So, what the heck is an expression tree. Well, it's not complicated but its not a walk in the park either. To quote ms:
那么,到底什么是表达式树。嗯,这并不复杂,但也不是在公园里散步。引用ms:
| Expression trees represent code in a tree-like data structure, where each node is an expression, for example, a method call or a binary operation such as x < y.
| 表达式树表示树状数据结构中的代码,其中每个节点都是一个表达式,例如,一个方法调用或一个二元运算(如 x < y)。
Simply put, an expression tree is a representation of a function as a collection of "actions".
简单地说,表达式树是将函数表示为“动作”的集合。
In the case of model=>model.SomeProperty, the expression tree would have a node in it that says: "Get 'Some Property' from a 'model'"
在 的情况下model=>model.SomeProperty,表达式树中会有一个节点,上面写着:“从‘模型’中获取‘某些属性’”
This expression tree can be compiledinto a function that can be invoked, but as long as it's an expression tree, it's just a collection of nodes.
这个表达式树可以编译成一个可以调用的函数,但是只要是一个表达式树,它就只是节点的集合。
So what is that good for?
那有什么用呢?
So Func<>or Action<>, once you have them, they are pretty much atomic. All you can really do is Invoke()them, aka tell them to
do the work they are supposed to do.
所以Func<>或者Action<>,一旦你拥有它们,它们就几乎是原子的。你真正能做的就是Invoke()他们,也就是告诉他们做他们应该做的工作。
Expression<Func<>>on the other hand, represents a collection of actions, which can be appended, manipulated, visited, or compiledand invoked.
Expression<Func<>>另一方面,表示一组动作,可以附加、操作、访问或编译和调用。
So why are you telling me all this?
那你为什么要告诉我这一切?
So with that understanding of what an Expression<>is, we can go back to Html.TextBoxFor. When it renders a textbox, it needs
to generate a few things aboutthe property that you are giving it. Things like attributeson the property for validation, and specifically
in this case it needs to figure out what to namethe <input>tag.
因此,了解了 anExpression<>是什么之后,我们可以回到Html.TextBoxFor. 当它呈现一个文本框时,它需要生成一些关于你给它的属性的东西。事情是这样attributes的财产进行验证,特别在这种情况下,需要弄清楚如何命名的<input>标签。
It does this by "walking" the expression tree and building a name. So for an expression like model=>model.SomeProperty, it walks the expression
gathering the properties that you are asking for and builds <input name='SomeProperty'>.
它通过“遍历”表达式树并构建名称来实现这一点。因此,对于像这样的表达式model=>model.SomeProperty,它会遍历该表达式,收集您要求并构建的属性<input name='SomeProperty'>。
For a more complicated example, like model=>model.Foo.Bar.Baz.FooBar, it might generate <input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />
对于更复杂的示例,例如model=>model.Foo.Bar.Baz.FooBar,它可能会生成<input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />
Make sense? It is not just the work that the Func<>does, but howit does its work is important here.
有道理?不仅仅是它所做的工作Func<>,它如何工作在这里也很重要。
(Note other frameworks like LINQ to SQL do similar things by walking an expression tree and building a different grammar, that this case a SQL query)
(注意 LINQ to SQL 等其他框架通过遍历表达式树并构建不同的语法来做类似的事情,在这种情况下是 SQL 查询)
How does the Model Binder work?
模型绑定器如何工作?
So once you get that, we have to briefly talk about the model binder. When the form gets posted, it's simply like a flat
Dictionary<string, string>, we have lost the hierarchical structure our nested view model may have had. It's the
model binder's job to take this key-value pair combo and attempt to rehydrate an object with some properties. How does it do
this? You guessed it, by using the "key" or name of the input that got posted.
所以一旦你明白了,我们必须简要地谈谈模型绑定器。当表单被发布时,它就像一个 flat
Dictionary<string, string>,我们失去了嵌套视图模型可能具有的层次结构。模型绑定器的工作是采用此键值对组合并尝试重新水化具有某些属性的对象。它是如何做到这一点的?您猜对了,通过使用发布的输入的“键”或名称。
So if the form post looks like
所以如果表单帖子看起来像
Foo.Bar.Baz.FooBar = Hello
And you are posting to a model called SomeViewModel, then it does the reverse of what the helper did in the first place. It looks for
a property called "Foo". Then it looks for a property called "Bar" off of "Foo", then it looks for "Baz"... and so on...
并且您要发布到一个名为 的模型SomeViewModel,然后它会执行与助手最初所做的相反的操作。它寻找一个名为“Foo”的属性。然后它从“Foo”中寻找一个名为“Bar”的属性,然后它寻找“Baz”……等等……
Finally it tries to parse the value into the type of "FooBar" and assign it to "FooBar".
最后,它尝试将值解析为“FooBar”的类型并将其分配给“FooBar”。
PHEW!!!
呼!!!
And voila, you have your model. The instance the Model Binder just constructed gets handed into requested Action.
瞧,你有你的模型。Model Binder 刚刚构建的实例被传递到请求的 Action 中。
So your solution doesn't work because the Html.[Type]For()helpers need an expression. And you are just giving them a value. It has no idea
what the context is for that value, and it doesn't know what to do with it.
所以你的解决方案不起作用,因为Html.[Type]For()助手需要一个表达式。而你只是给了他们一个价值。它不知道该值的上下文是什么,也不知道如何处理它。
Now some people suggested using partials to render. Now this in theory will work, but probably not the way that you expect. When you render a partial, you are changing the type of TModel, because you are in a different view context. This means that you can describe
your property with a shorter expression. It also means when the helper generates the name for your expression, it will be shallow. It
will only generate based on the expression it's given (not the entire context).
现在有人建议使用局部渲染。现在这在理论上可行,但可能不是您期望的方式。当您渲染部分时,您正在更改 的类型TModel,因为您处于不同的视图上下文中。这意味着您可以用更短的表达方式来描述您的财产。这也意味着当助手为您的表达式生成名称时,它会很浅。它只会根据给定的表达式(而不是整个上下文)生成。
So lets say you had a partial that just rendered "Baz" (from our example before). Inside that partial you could just say:
因此,假设您有一个部分渲染了“Baz”(来自我们之前的示例)。在那个部分你可以说:
@Html.TextBoxFor(model=>model.FooBar)
Rather than
而不是
@Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
That means that it will generate an input tag like this:
这意味着它将生成一个输入标签,如下所示:
<input name="FooBar" />
Which, if you are posting this form to an action that is expecting a large deeply nested ViewModel, then it will try to hydrate a property
called FooBaroff of TModel. Which at best isn't there, and at worst is something else entirely. If you were posting to a specific action that was accepting a Baz, rather than the root model, then this would work great! In fact, partials are a good way to change your view context, for example if you had a page with multiple forms that all post to different actions, then rendering a partial for each one would be a great idea.
其中,如果您正在张贴这种形式给需要大深度嵌套视图模型的动作,那么它会尝试水合物称为财产FooBar的关闭TModel。最好的情况是不存在,最坏的情况是完全不同的东西。如果您要发布到接受Baz,而不是根模型的特定操作,那么这会很好用!事实上,局部是改变你的视图上下文的好方法,例如,如果你有一个页面有多个表单,所有表单都发布到不同的动作,那么为每个表单渲染一个局部将是一个好主意。
Now once you get all of this, you can start to do really interesting things with Expression<>, by programatically extending them and doing
other neat things with them. I won't get into any of that. But, hopefully, this will
give you a better understanding of what is going on behind the scenes and why things are acting the way that they are.
现在一旦你掌握了所有这些,你就可以开始用 来做真正有趣的事情Expression<>,通过以编程方式扩展它们并用它们做其他巧妙的事情。我不会涉足这些。但是,希望这能让您更好地了解幕后发生的事情以及事情为什么会这样。
回答by Adrian Thompson Phillips
You could add a Category partial and a Product partial, each would take a smaller part of the main model as it's own model, i.e. Category's model type might be an IEnumerable, you would pass in Model.Theme to it. The Product's partial might be an IEnumerable that you pass Model.Products into (from within the Category partial).
您可以添加一个 Category 部分和一个 Product 部分,每个部分都会占用主模型的一小部分,因为它是自己的模型,即 Category 的模型类型可能是 IEnumerable,您可以将 Model.Theme 传递给它。Product 的部分可能是一个 IEnumerable,您将 Model.Products 传入(从 Category 部分中)。
I'm not sure if that would be the right way forward, but would be interested in knowing.
我不确定这是否是正确的前进方向,但有兴趣知道。
EDIT
编辑
Since posting this answer, I've used EditorTemplates and find this the easiest way to handle repeating input groups or items. It handles all your validation message problems and form submission/model binding woes automatically.
自从发布此答案后,我使用了 EditorTemplates 并发现这是处理重复输入组或项目的最简单方法。它会自动处理您所有的验证消息问题和表单提交/模型绑定问题。
回答by Manas
It is clear from the error.
从错误中可以看出。
The HtmlHelpers appended with "For" expects lambda expression as a parameter.
附加有“For”的 HtmlHelpers 需要 lambda 表达式作为参数。
If you are passing the value directly, better use Normal one.
如果直接传递值,最好使用 Normal 。
e.g.
例如
Instead of TextboxFor(....) use Textbox()
而不是 TextboxFor(....) 使用 Textbox()
syntax for TextboxFor will be like Html.TextBoxFor(m=>m.Property)
TextboxFor 的语法类似于 Html.TextBoxFor(m=>m.Property)
In your scenario you can use basic for loop, as it will give you index to use.
在您的场景中,您可以使用基本的 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)
}
}
}
回答by Alireza Sabouri
You can simply use EditorTemplates to do that, you need to create a directory named "EditorTemplates" in your controller's view folder and place a seperate view for each of your nested entities (named as entity class name)
您可以简单地使用 EditorTemplates 来做到这一点,您需要在控制器的视图文件夹中创建一个名为“EditorTemplates”的目录,并为每个嵌套实体放置一个单独的视图(命名为实体类名称)
Main view :
主要观点:
@model ViewModels.MyViewModels.Theme
@Html.LabelFor(Model.Theme.name)
@Html.EditorFor(Model.Theme.Categories)
Category view (/MyController/EditorTemplates/Category.cshtml) :
类别视图(/MyController/EditorTemplates/Category.cshtml):
@model ViewModels.MyViewModels.Category
@Html.LabelFor(Model.Name)
@Html.EditorFor(Model.Products)
Product view (/MyController/EditorTemplates/Product.cshtml) :
产品视图 (/MyController/EditorTemplates/Product.cshtml) :
@model ViewModels.MyViewModels.Product
@Html.LabelFor(Model.Name)
@Html.EditorFor(Model.Orders)
and so on
等等
this way Html.EditorFor helper will generate element's names in an ordered manner and therefore you won't have any further problem for retrieving the posted Theme entity as a whole
这样 Html.EditorFor 助手将以有序的方式生成元素的名称,因此您将不会有任何进一步的问题来检索整个发布的 Theme 实体
回答by Pranav Labhe
When you are using foreach loop within view for binded model ... Your model is supposed to be in listed format.
当您在绑定模型的视图中使用 foreach 循环时......您的模型应该是列出的格式。
i.e
IE
@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>
}
}
回答by FirstDivision
Another much simpler possibility is that one of your property names is wrong (probably one you just changed in the class). This is what it was for me in RazorPages .NET Core 3.
另一种更简单的可能性是您的一个属性名称是错误的(可能是您刚刚在课堂上更改的)。这就是 RazorPages .NET Core 3 中对我的意义。

