The quick answer is to use a for() loop instead 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 is silent about why this fixes the problem.
There are three things that you have at least a quick understanding before you can solve this problem. I have to admit that I am a truck for a long time, when I started working with the frame. And it took me quite a while to really get what was happening.
These are three things:
- How do
LabelFor and others ...For helpers work in MVC? - What is an expression tree?
- How does the Model Linker work?
All three concepts combine to produce an answer.
How LabelFor and others ...For helpers work in MVC?
So, you used the HtmlHelper<T> LabelFor for LabelFor and TextBoxFor and others, and you probably noticed that when you call them, you pass them a lambda, and it magically generates some html. But how?
So, the first thing to notice is the signature for these assistants. Let's look at the simplest overload for TextBoxFor
public static MvcHtmlString TextBoxFor<TModel, TProperty>( this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression )
Firstly, it is an extension method for a strongly typed HtmlHelper , such as <TModel> . So, just what happens behind the scenes, when the razor displays this view, it generates a class. Inside this class there is an instance of HtmlHelper<TModel> (as an Html property, so you can use @Html... ), where TModel is the type defined in your @model . So, in your case, when you look at this view, TModel will always be of type ViewModels.MyViewModels.Theme .
Now the next argument is a bit more complicated. So let's look at the challenge
@Html.TextBoxFor(model=>model.SomeProperty);
Looks like we have a little lambda. And if you guess the signature, you might think that the type for this argument would be just Func<TModel, TProperty> , where TModel is the type of the view model and TProperty is called as the type of the property.
But this is not entirely correct if you look at the actual type of the argument Expression<Func<TModel, TProperty>> .
So, when you usually generate lambda, the compiler takes the lambda and compiles it to MSIL, like any other (so you can use delegates, method groups and lambdas more or less interchangeably because they are just code references.)
However, when the compiler sees that the type is Expression<> , it does not immediately compile the lambda to MSIL, instead it generates an Expression Tree!
So what is this expression tree. Well, it’s not difficult, but not a walk in the park. To specify ms:
| Expression trees represent code in a tree-like data structure, where each node is an expression, such as 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".
In the case of model=>model.SomeProperty there will be a node in the expression tree that says: "Get" Some property "from" model "
This expression tree can be compiled into a function that can be called, but as long as it is an expression tree, it is just a collection of nodes.
So what's good for?
So Func<> or Action<> , once you get them, they are pretty much atomic. All you really can do is Invoke() them, and also tell them to do the work they have to do.
Expression<Func<>> , is a set of actions that can be added, processed, visited or compiled and called.
So why are you telling me all this?
So, with an understanding of what Expression<> , we can return to Html.TextBoxFor . When he creates a text box, he needs to generate a few things about the property that you give him. Things like attributes for the property to check, and in particular in this case, he needs to figure out what the <input> tag is called.
He does this by “walking” the expression tree and creating a name. Thus, for an expression of type model=>model.SomeProperty it executes the expression by collecting the properties that you request and builds <input name='SomeProperty'> .
For a more complex example, for example model=>model.Foo.Bar.Baz.FooBar , it can generate <input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />
Make sense? This is not just the job Func<> does, but how she does her job here.
(Note that other frameworks, such as LINQ to SQL, perform similar actions by moving the expression tree and creating another grammar, in this case an SQL query)
How does the binder model work?
So, once you understand this, we need to briefly talk about model binding. When the form is submitted, it simply resembles the apartment Dictionary<string, string> , we lost the hierarchical structure that our nested representation model could have. This is a model to take this key-value pair combination and try to translate an object with some properties. How to do it? You guessed it using the "key" or the name of the message you entered.
So, if the form message looks like
Foo.Bar.Baz.FooBar = Hello
And you submit to a model called SomeViewModel , then it does the opposite of what the helper did first. He is looking for the property "Foo". Then he searches for the property "Bar" off "Foo", then he searches for the "Baz" ... and so on ...
Finally, he tries to parse the value as "FooBar" and assign it to "FooBar".
Phew !!!
And voila, you have your model. The instance that the Binder model has just built is passed to the requested action.
So your solution does not work, because Html.[Type]For() helpers need an expression. And you just give them value. It has no idea what context is for this value, and he does not know what to do with it.
Now some people have suggested using partials for rendering. Now this will theoretically work, but probably not the way you expect. When you do partial, you change 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 that the helper generates a name for your expression, it will be small. This will only be generated based on the expression it expresses (and not the entire context).
So, let's say that you had a partial that “Baz” had just appeared (from our example earlier). Inside this part you could just say:
@Html.TextBoxFor(model=>model.FooBar)
Instead
@Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
This means that it will generate an input tag as follows:
<input name="FooBar" />
What if you submit this form to an action that expects a large, deeply nested ViewModel, then it will try to FooBar property called FooBar off TModel . Which at best does not exist, and at worst is something completely different. If you send to a specific action that takes a Baz , not a root model, then that would be great! In fact, partial files are a good way to change the context of the presentation, for example, if you have a page with several forms that all submit to different actions, then providing partial for each of them would be a great idea.
Now that you have all this, you can start doing really interesting things with Expression<> , expanding them programmatically and doing other neat things with them. I will not delve into this. But hopefully this will give you a better idea of what is going on behind the scenes and why everything works the way they are.