La réponse rapide est d'utiliser un for()
à la place de votre foreach()
boucles. Quelque chose comme :
@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)
}
}
}
Mais cela ne tient pas compte pourquoi cela corrige le problème.
Il y a trois choses que vous devez comprendre au moins sommairement avant de pouvoir résoudre ce problème. Je dois admettre que je cargo-culte ce depuis longtemps quand j'ai commencé à travailler avec le framework. Et ça m'a pris un certain temps pour vraiment comprendre ce qui se passait.
Ces trois choses sont :
- Comment les
LabelFor
et autres ...For
Les aides fonctionnent en MVC ?
- Qu'est-ce qu'un arbre d'expression ?
- Comment fonctionne le Model Binder ?
Ces trois concepts sont liés entre eux pour obtenir une réponse.
Comment les LabelFor
et autres ...For
Les aides fonctionnent en MVC ?
Donc, vous avez utilisé le HtmlHelper<T>
extensions pour LabelFor
y TextBoxFor
et autres, et vous avez probablement remarqué que lorsque vous les invoquez, vous leur passez un lambda et il par magie génère du html. Mais comment ?
La première chose à remarquer est donc la signature de ces aides. Regardons la surcharge la plus simple de TextBoxFor
public static MvcHtmlString TextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
Tout d'abord, il s'agit d'une méthode d'extension pour un système fortement typé. HtmlHelper
de type <TModel>
. Donc, pour simplement ce qui se passe en coulisses, lorsque Razor rend cette vue, il génère une classe. A l'intérieur de cette classe se trouve une instance de HtmlHelper<TModel>
(comme la propriété Html
c'est pourquoi vous pouvez utiliser @Html...
), où TModel
est le type défini dans votre @model
déclaration. Donc dans votre cas, quand vous regardez cette vue TModel
sera toujours du type ViewModels.MyViewModels.Theme
.
Maintenant, l'argument suivant est un peu délicat. Regardons donc une invocation
@Html.TextBoxFor(model=>model.SomeProperty);
On dirait que nous avons un petit lambda, et si l'on devait deviner la signature, on pourrait penser que le type de cet argument serait simplement un Func<TModel, TProperty>
, donde TModel
est le type du modèle de vue et TProperty
est déduit comme le type de la propriété.
Mais ce n'est pas tout à fait exact, si vous regardez le réel type de l'argument son Expression<Func<TModel, TProperty>>
.
Ainsi, lorsque vous générez normalement un lambda, le compilateur prend le lambda et le compile en MSIL, comme n'importe quel autre fichier de type fonction (c'est pourquoi vous pouvez utiliser les délégués, les groupes de méthodes et les lambdas de manière plus ou moins interchangeable, parce qu'il s'agit simplement de références de code). références de code).
Cependant, lorsque le compilateur voit que le type est un Expression<>
il ne compile pas immédiatement le lambda en MSIL, mais génère un message de type arbre d'expression !
Alors, que diable est un arbre d'expression. Eh bien, ce n'est pas compliqué, mais ce n'est pas non plus une promenade de santé. Pour citer Mme :
| Les arbres d'expression représentent le code dans une structure de données arborescente, où chaque nœud est une expression, par exemple, un appel de méthode ou une opération binaire telle que x < y.
En termes simples, un arbre d'expression est une représentation d'une fonction comme une collection d'"actions".
Dans le cas de model=>model.SomeProperty
l'arbre d'expression contiendrait un nœud qui dirait : "Get 'Some Property' from a 'model'".
Cet arbre d'expression peut être compilé en une fonction qui peut être invoquée, mais tant que c'est un arbre d'expression, c'est juste une collection de nœuds.
Alors à quoi ça sert ?
Alors Func<>
o Action<>
une fois que vous les avez, ils sont pratiquement atomiques. Tout ce que vous pouvez faire, c'est Invoke()
les, aka leur dire de faire le travail qu'ils sont censés faire.
Expression<Func<>>
d'autre part, représente une collection d'actions, qui peuvent être ajoutées, manipulées, a visité o compilé et invoqué.
Alors pourquoi tu me dis tout ça ?
Donc avec cette compréhension de ce qu'est un Expression<>
est, nous pouvons retourner à Html.TextBoxFor
. Lorsqu'il rend une zone de texte, il doit de générer quelques éléments à propos de la propriété que vous lui donnez. Des choses comme attributes
sur la propriété pour validation, et notamment dans ce cas, elle doit trouver quoi faire pour nom le site <input>
étiquette.
Pour ce faire, il "parcourt" l'arbre des expressions et construit un nom. Ainsi, pour une expression comme model=>model.SomeProperty
, il marche l'expression en rassemblant les propriétés que vous demandez et construit <input name='SomeProperty'>
.
Pour un exemple plus compliqué, comme model=>model.Foo.Bar.Baz.FooBar
il pourrait générer <input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />
C'est logique ? Ce n'est pas seulement le travail que le Func<>
le fait, mais comment La façon dont il effectue son travail est importante ici.
(Notez que d'autres frameworks comme LINQ to SQL font des choses similaires en parcourant un arbre d'expression et en construisant une grammaire différente, dans ce cas une requête SQL).
Comment fonctionne le Model Binder ?
Une fois que vous avez compris cela, nous devons parler brièvement du modèle de liant. Lorsque le formulaire est posté, c'est simplement comme un plat Dictionary<string, string>
Nous avons perdu la structure hiérarchique que notre modèle de vue imbriquée pouvait avoir. C'est le travail du de prendre cette combinaison de paires clé-valeur et de tenter de réhydrater un objet avec certaines propriétés. Comment s'y prend-il ? cela ? Vous l'avez deviné, en utilisant la "clé" ou le nom de l'entrée qui a été postée.
Donc, si le message du formulaire ressemble à
Foo.Bar.Baz.FooBar = Hello
Et vous vous connectez à un modèle appelé SomeViewModel
alors il fait l'inverse de ce que l'assistant a fait en premier lieu. Il recherche une propriété appelée "Foo". Puis il cherche une propriété appelée "Bar" à partir de "Foo", puis il cherche "Baz"... et ainsi de suite...
Enfin, il essaie d'analyser la valeur dans le type "FooBar" et de l'affecter à "FooBar".
PHEW ! !!
Et voilà, vous avez votre modèle. L'instance que le Model Binder vient de construire est transmise à l'Action demandée.
Donc votre solution ne fonctionne pas parce que le Html.[Type]For()
Les aides ont besoin d'une expression. Et vous ne faites que leur donner une valeur. Il n'a aucune idée du contexte de cette valeur, et il ne sait pas quoi en faire.
Certaines personnes ont suggéré d'utiliser des partiels pour le rendu. En théorie, cela fonctionne, mais probablement pas de la façon dont vous l'attendez. Lorsque vous effectuez le rendu d'un partial, vous changez le type de TModel
parce que vous êtes dans un contexte de vue différent. Cela signifie que vous pouvez décrire votre propriété avec une expression plus courte. Cela signifie également que lorsque l'assistant génère le nom de votre expression, il sera peu profond. Elle générera uniquement le nom basé sur l'expression qui lui est donnée (et non sur le contexte entier).
Disons que vous avez un partiel qui a juste rendu "Baz" (de notre exemple précédent). A l'intérieur de ce partiel, vous pourriez simplement dire :
@Html.TextBoxFor(model=>model.FooBar)
Plutôt que de
@Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
Cela signifie qu'il va générer une balise d'entrée comme celle-ci :
<input name="FooBar" />
Si vous envoyez ce formulaire à une action qui attend un ViewModel important et profondément imbriqué, il essaiera d'hydrater une propriété. appelée FooBar
de TModel
. Ce qui, au mieux, n'existe pas, et au pire, est quelque chose d'entièrement différent. Si vous envoyez un message à une action spécifique qui accepte un message de type Baz
plutôt que le modèle Root, alors cela fonctionnera parfaitement ! En fait, les partiels sont un bon moyen de modifier le contexte d'affichage, par exemple si vous avez une page avec plusieurs formulaires qui renvoient tous à des actions différentes, le rendu d'un partiel pour chacun d'eux serait une bonne idée.
Maintenant, une fois que vous avez tout ça, vous pouvez commencer à faire des choses intéressantes avec Expression<>
en les étendant par programme et en faisant d'autres choses intéressantes avec eux. Je ne vais pas m'étendre sur ce sujet. Mais, avec un peu de chance, cela vous donner une meilleure compréhension de ce qui se passe dans les coulisses et pourquoi les choses agissent comme elles le font.
1 votes
Ne devriez-vous pas avoir
@
avant tous lesforeach
? Ne devriez-vous pas également avoir des lambdas dansHtml.EditorFor
(Html.EditorFor(m => m.Note)
, par exemple) et le reste des méthodes? Je peux me tromper, mais pouvez-vous s'il vous plaît coller votre code réel? Je suis assez nouveau dans MVC, mais vous pouvez le résoudre assez facilement avec des vues partielles, ou des éditeurs (si c'est le nom?).0 votes
category.nom
Je suis sûr que c'est unechaîne de caractères
et...For
ne prend pas en charge une chaîne de caractères en tant que premier paramètre0 votes
Oui, j'ai juste manqué les @, maintenant ajoutés. Merci. Cependant, en ce qui concerne lambda, si je commence à taper @Html.TextBoxFor(m => m. alors je ne semble obtenir qu'une référence à l'objet Model principal, pas ceux à l'intérieur de la boucle foreach.
0 votes
@DavidC - Je ne connais pas assez de MVC 3 pour répondre pour le moment - mais je soupçonne que c'est votre problème
:)
.2 votes
Je suis dans le train, mais si cela n'est pas répondu d'ici le moment où j'arrive au travail, je posterai une réponse. La réponse rapide est d'utiliser un
for()
ordinaire plutôt qu'unforeach
. Je vais expliquer pourquoi, car cela m'a aussi beaucoup troublé pendant longtemps.0 votes
Ah je comprends ce que tu veux dire, alors tu peux utiliser : LabelFor(m => m.Theme[0].nom), etc.