MVC Razor 视图嵌套 foreach 的模型 [英] MVC Razor view nested foreach's model

查看:29
本文介绍了MVC Razor 视图嵌套 foreach 的模型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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....

但这就是场景

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

Theme contains List Category contains List Product contains List

My Controller 提供了一个完全填充的主题,其中包含该主题的所有类别、该类别中的产品及其订单.

My Controller provides a fully populated Theme, with all the Categories for that theme, the Products within this categories and the their orders.

orders 集合有一个名为 Quantity(以及许多其他属性)的属性,需要对其进行编辑.

The orders collection has a property called Quantity (amongst many others) that needs to be editable.

@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 循环中的那些.

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.

我在那里尝试做的事情是可能的,还是我高估或误解了可能的事情?

Is what I'm trying to do there even possible or have I overestimated or misunderstood what is possible?

根据上述内容,我在 TextboxFor、EditorFor 等上出现错误

With the above I get an error on the TextboxFor, EditorFor, etc

CS0411:方法的类型参数'System.Web.Mvc.Html.InputExtensions.TextBoxFor(System.Web.Mvc.HtmlHelper,System.Linq.Expressions.Expression>)'不能从用法推断.尝试指定类型参数明确的.

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.

谢谢.

推荐答案

快速答案是使用 for() 循环代替 foreach() 循环.类似的东西:

The quick answer is to use a for() loop in place of your foreach() loops. Something like:

@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 why this 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-culted this 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.

这三件事是:

  • LabelFor 和其他 ...For 助手如何在 MVC 中工作?
  • 什么是表达式树?
  • 模型绑定器是如何工作的?
  • How do the LabelFor and other ...For helpers work in MVC?
  • What is an Expression Tree?
  • How does the Model Binder work?

所有这三个概念都链接在一起以获得答案.

All three of these concepts link together to get an answer.

因此,您已经为 LabelForTextBoxFor 等使用了 HtmlHelper 扩展,并且你可能注意到,当你调用它们时,你传递给它们一个 lambda 并且它神奇地生成一些html.但是如何?

So, you've used the HtmlHelper<T> extensions for LabelFor and TextBoxFor and others, and you probably noticed that when you invoke them, you pass them a lambda and it magically generates some html. But how?

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

So the first thing to notice is the signature for these helpers. Lets look at the simplest overload for TextBoxFor

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

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

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 TModel is the type defined in your @model statement. So in your case, when you are looking at this view TModel will always be of the type ViewModels.MyViewModels.Theme.

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

Now, the next argument is a bit tricky. So lets look at an invocation

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

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

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 TModel is the type of the view model and TProperty is inferred as the type of the property.

但这并不完全正确,如果您查看参数的实际类型,它的Expression>.

But thats not quite right, if you look at the actual type of the argument its Expression<Func<TModel, TProperty>>.

因此,当您通常生成 lambda 时,编译器会获取 lambda 并将其编译为 MSIL,就像任何其他函数(这就是为什么您可以或多或少地互换使用委托、方法组和 lambda,因为它们只是代码参考.)

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.)

但是,当编译器发现类型是 Expression<> 时,它不会立即将 lambda 编译为 MSIL,而是生成一个表达式树!

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!

那么,表达式树到底是什么.嗯,这并不复杂,但也不是在公园里散步.引用ms:

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:

|表达式树表示树状数据结构中的代码,其中每个节点都是一个表达式,例如一个方法调用或一个二元运算如x<;y.

| 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.

简单地说,表达式树是将函数表示为动作"的集合.

Simply put, an expression tree is a representation of a function as a collection of "actions".

model=>model.SomeProperty 的情况下,表达式树中会有一个节点,上面写着:从 'model' 中获取 'Some Property'"

In the case of model=>model.SomeProperty, the expression tree would have a node in it that says: "Get 'Some Property' from a 'model'"

这个表达式树可以编译成一个可以调用的函数,但只要是一个表达式树,它就只是节点的集合.

This expression tree can be compiled into a function that can be invoked, but as long as it's an expression tree, it's just a collection of nodes.

所以 Func<>Action<>,一旦你有了它们,它们就几乎是原子的.你真正能做的就是 Invoke() 他们,也就是告诉他们做他们应该做的工作.

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.

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

Expression<Func<>> on the other hand, represents a collection of actions, which can be appended, manipulated, visited, or compiled and invoked.

因此,了解了 Expression<> 是什么之后,我们可以回到 Html.TextBoxFor.当它呈现一个文本框时,它需要生成一些关于您所提供的属性的信息.诸如 attributes 之类的用于验证的属性,特别是在这种情况下,它需要弄清楚 命名 标签的内容.

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 about the property that you are giving it. Things like attributes on the property for validation, and specifically in this case it needs to figure out what to name the <input> tag.

它通过遍历"表达式树并构建名称来实现这一点.所以对于像 model=>model.SomeProperty 这样的表达式,它会遍历表达式收集您要求的属性并构建 .

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.Foo.Bar.Baz.FooBar,它可能会生成

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]" />

有意义吗?不只是 Func<> 所做的工作,如何在这里工作也很重要.

Make sense? It is not just the work that the Func<> does, but how it does its work is important here.

(注意像 LINQ to SQL 这样的其他框架通过遍历表达式树和构建不同的语法来做类似的事情,在这种情况下是一个 SQL 查询)

(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)

所以一旦你明白了,我们就必须简要讨论一下模型绑定器.当表格被张贴时,它就像一个公寓Dictionary,我们已经失去了嵌套视图模型可能具有的层次结构.这是模型绑定器的工作是采用此键值对组合并尝试使用某些属性重新水化对象.它是怎么做的这个?您猜对了,通过使用发布的输入的键"或名称.

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.

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

So if the form post looks like

Foo.Bar.Baz.FooBar = Hello

并且您将发布到一个名为 SomeViewModel 的模型,然后它会执行与助手最初所做的相反的操作.它寻找一个名为Foo"的属性.然后它从Foo"中寻找一个名为Bar"的属性,然后它寻找Baz"……等等……

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...

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

Finally it tries to parse the value into the type of "FooBar" and assign it to "FooBar".

PHEW!!!

瞧,你有你的模型.Model Binder 刚刚构建的实例被传递到请求的 Action 中.

And voila, you have your model. The instance the Model Binder just constructed gets handed into requested Action.

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

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.

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

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).

假设您有一个部分渲染了Baz"(来自我们之前的示例).在那个部分你可以说:

So lets say you had a partial that just rendered "Baz" (from our example before). Inside that partial you could just say:

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

而不是

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

这意味着它将生成这样的输入标签:

That means that it will generate an input tag like this:

<input name="FooBar" />

其中,如果您将此表单发布到需要大型深度嵌套 ViewModel 的操作,那么它将尝试对属性进行水合从 TModel 中调用 FooBar.最好的情况是不存在,最坏的情况是完全不同的东西.如果您要发布到接受 Baz 而不是根模型的特定操作,那么这会很好用!事实上,partials 是改变视图上下文的好方法,例如,如果您有一个页面包含多个表单,所有表单都发布到不同的操作,那么为每个表单渲染一个 partial 将是一个好主意.

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 FooBar off 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.

现在,一旦你掌握了所有这些,你就可以开始用 Expression<> 做一些非常有趣的事情,通过以编程方式扩展它们并执行其他整洁的东西.我不会涉足这些.但是,希望这会让您更好地了解幕后发生的事情以及事情为什么会这样.

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.

这篇关于MVC Razor 视图嵌套 foreach 的模型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆