Imaginez un scénario commun, c'est une version plus simple de ce que je rencontre. J'ai en fait quelques couches de nidification supplémentaire sur la mienne ....
Mais c'est le scénario
Le thème contient la liste La catégorie contient la liste Le produit contient la liste
Mon contrôleur fournit un thème entièrement rempli, avec toutes les catégories pour ce thème, les produits de ces catégories et leurs commandes.
La collection de commandes a une propriété appelée Quantity (parmi beaucoup d'autres) qui doit être modifiable.
@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)
}
}
}
Si j'utilise lambda à la place, il semble que je n'obtienne qu'une référence à l'objet Top Model, "Theme" et non à ceux de la boucle foreach.
Est-ce que ce que j'essaie de faire là-bas est même possible ou ai-je surestimé ou mal compris ce qui est possible?
Avec ce qui précède, j'obtiens une erreur sur TextboxFor, EditorFor, etc.
CS0411: Les arguments de type pour la méthode 'System.Web.Mvc.Html.InputExtensions.TextBoxFor (System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>)' ne peuvent pas être déduits de l'utilisation. Essayez de spécifier les arguments de type explicitement.
Merci.
@
avant toutforeach
s? Ne devriez-vous pas également avoir des lambdas dansHtml.EditorFor
(Html.EditorFor(m => m.Note)
par exemple) et le reste des méthodes? Je me trompe peut-être, mais pouvez-vous coller votre code actuel? Je suis assez nouveau dans MVC, mais vous pouvez le résoudre assez facilement avec des vues partielles ou des éditeurs (si tel est le nom?).category.name
Je suis sûr que c'est unstring
et...For
ne prend pas en charge une chaîne comme premier paramètre:)
.for()
plutôt qu'unforeach
. Je vais vous expliquer pourquoi, parce que cela m'a aussi troublé pendant longtemps.Réponses:
La réponse rapide est d'utiliser une
for()
boucle à la place de vosforeach()
boucles. Quelque chose comme:Mais cela passe sous silence pourquoi cela résout le problème.
Il y a trois choses que vous avez au moins une compréhension superficielle avant de pouvoir résoudre ce problème. Je dois admettre que j'ai cultivé cela pendant longtemps lorsque j'ai commencé à travailler avec le framework. Et il m'a fallu un certain temps pour vraiment comprendre ce qui se passait.
Ces trois choses sont:
LabelFor
et les autres...For
dans MVC?Ces trois concepts sont liés pour obtenir une réponse.
Comment fonctionnent les assistants
LabelFor
et les autres...For
dans MVC?Donc, vous avez utilisé les
HtmlHelper<T>
extensions pourLabelFor
etTextBoxFor
et d'autres, et vous avez probablement remarqué que lorsque vous les appelez, vous leur passez un lambda et cela génère comme par magie du html. Mais comment?La première chose à remarquer est donc la signature de ces assistants. Regardons la surcharge la plus simple pour
TextBoxFor
Tout d' abord, cela est une méthode d'extension pour un fortement typé
HtmlHelper
, de type<TModel>
. Donc, pour indiquer simplement ce qui se passe dans les coulisses, lorsque Razor rend cette vue, il génère une classe. À l'intérieur de cette classe se trouve une instance deHtmlHelper<TModel>
(en tant que propriétéHtml
, c'est pourquoi vous pouvez utiliser@Html...
), oùTModel
est le type défini dans votre@model
instruction. Donc, dans votre cas, lorsque vous regardez cette vueTModel
sera toujours du typeViewModels.MyViewModels.Theme
.Maintenant, l'argument suivant est un peu délicat. Alors regardons une invocation
On dirait que nous avons un petit lambda, et si l'on devinait la signature, on pourrait penser que le type de cet argument serait simplement a
Func<TModel, TProperty>
, oùTModel
est le type du modèle de vue etTProperty
est déduit comme le type de la propriété.Mais ce n'est pas tout à fait vrai, si vous regardez le type réel 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 dans MSIL, comme toute autre fonction (c'est pourquoi vous pouvez utiliser des délégués, des groupes de méthodes et des lambdas de manière plus ou moins interchangeable, car ce ne sont que des références de code .)
Cependant, lorsque le compilateur voit que le type est an
Expression<>
, il ne compile pas immédiatement le lambda vers MSIL, il génère plutôt un arbre d'expression!Qu'est-ce qu'un arbre d'expression ?
Alors, qu'est-ce que diable est un arbre d'expression. Eh bien, ce n'est pas compliqué mais ce n'est pas non plus une promenade dans le parc. Pour citer ms:
| 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 en tant que collection "d'actions".
Dans le cas de
model=>model.SomeProperty
, l'arborescence des expressions contiendrait un nœud qui dit: "Obtenir 'une propriété' à partir d'un 'modèle'"Cet arbre d'expression peut être compilé en une fonction qui peut être appelée, mais tant qu'il s'agit d'un arbre d'expression, ce n'est qu'une collection de nœuds.
Alors, à quoi ça sert?
Donc
Func<>
ouAction<>
, une fois que vous les avez, ils sont à peu près atomiques. Tout ce que vous pouvez vraiment faire, c'estInvoke()
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, visitées ou compilées et appelées.Alors pourquoi tu me dis tout ça?
Donc, avec cette compréhension de ce qu'est un
Expression<>
, nous pouvons revenir àHtml.TextBoxFor
. Lorsqu'il rend une zone de texte, il doit générer quelques éléments sur la propriété que vous lui attribuez. Des choses commeattributes
sur la propriété pour la validation, et en particulier dans ce cas, il doit déterminer comment nommer la<input>
balise.Il le fait en "parcourant" l'arborescence des expressions et en créant un nom. Donc, pour une expression comme
model=>model.SomeProperty
, elle parcourt l'expression 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
, cela pourrait générer<input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />
Ça a du sens? Ce n'est pas seulement le travail que le
Func<>
fait, mais la manière dont il fait son travail est important 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, que dans ce cas une requête SQL)
Comment fonctionne le classeur de modèles?
Donc, une fois que vous avez compris, nous devons parler brièvement du classeur modèle. Lorsque le formulaire est publié, c'est tout simplement comme un appartement
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 classeur de modèles de prendre cette combinaison de paires clé-valeur et d'essayer de réhydrater un objet avec certaines propriétés. Comment fait-il cela? Vous l'avez deviné, en utilisant la "clé" ou le nom de l'entrée qui a été publiée.Donc, si le message du formulaire ressemble à
Et vous publiez sur un modèle appelé
SomeViewModel
, puis il fait l'inverse de ce que l'assistant a fait en premier lieu. Il recherche une propriété appelée "Foo". Ensuite, il cherche une propriété appelée "Bar" hors de "Foo", puis il cherche "Baz" ... et ainsi de suite ...Enfin, il essaie d'analyser la valeur dans le type de "FooBar" et de l'affecter à "FooBar".
PHEW!!!
Et voila, vous avez votre modèle. L'instance que le Model Binder vient de construire est transmise à l'action demandée.
Votre solution ne fonctionne donc pas car les
Html.[Type]For()
assistants ont besoin d'une expression. Et vous leur donnez simplement une valeur. Il n'a aucune idée du contexte pour cette valeur, et il ne sait pas quoi en faire.Maintenant, certaines personnes ont suggéré d'utiliser des partiels pour le rendu. Maintenant, cela fonctionnera en théorie, mais probablement pas de la manière que vous attendez. Lorsque vous effectuez le rendu d'un partiel, vous modifiez le type de
TModel
, car 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. Il ne sera généré qu'en fonction de l'expression donnée (pas du contexte entier).Disons donc que vous avez eu un partiel qui vient de rendre "Baz" (de notre exemple avant). À l'intérieur de ce partiel, vous pourriez simplement dire:
Plutôt que
Cela signifie qu'il générera une balise d'entrée comme celle-ci:
Ce qui, si vous envoyez des messages ce formulaire à une action qui attend un grand ViewModel, il essayera profondément imbriquées pour hydrater une propriété appelée
FooBar
hors deTModel
. Ce qui, au mieux, n'existe pas, et au pire, c'est tout autre chose. Si vous publiez sur une action spécifique qui acceptait unBaz
modèle, plutôt que le modèle racine, cela fonctionnerait très bien! En fait, les partiels sont un bon moyen de modifier votre contexte d'affichage, par exemple si vous aviez une page avec plusieurs formulaires qui publient tous des actions différentes, alors rendre un partiel pour chacun serait une excellente idée.Maintenant, une fois que vous avez tout cela, vous pouvez commencer à faire des choses vraiment intéressantes
Expression<>
, en les étendant par programme et en faisant d'autres choses intéressantes avec eux. Je n'entrerai pas dans tout cela. Mais, espérons-le, cela vous permettra de mieux comprendre ce qui se passe dans les coulisses et pourquoi les choses se comportent comme elles le sont.la source
Vous pouvez simplement utiliser EditorTemplates pour ce faire, vous devez créer un répertoire nommé "EditorTemplates" dans le dossier de vue de votre contrôleur et placer une vue séparée pour chacune de vos entités imbriquées (nommée comme nom de classe d'entité)
Vue principale :
Affichage des catégories (/MyController/EditorTemplates/Category.cshtml):
Vue produit (/MyController/EditorTemplates/Product.cshtml):
etc
De cette façon, Html.EditorFor helper générera les noms des éléments de manière ordonnée et vous n'aurez donc plus de problème pour récupérer l'entité Theme publiée dans son ensemble
la source
Vous pouvez ajouter un partiel de catégorie et un partiel de produit, chacun prenant une partie plus petite du modèle principal comme son propre modèle, c'est-à-dire que le type de modèle de la catégorie peut être un IEnumerable, vous lui passerez Model.Theme. Le partiel du produit peut être un IEnumerable dans lequel vous passez Model.Products (à partir du partiel de catégorie).
Je ne sais pas si ce serait la bonne voie à suivre, mais je serais intéressé à savoir.
ÉDITER
Depuis la publication de cette réponse, j'ai utilisé EditorTemplates et je trouve que c'est le moyen le plus simple de gérer des groupes ou des éléments d'entrée répétitifs. Il gère automatiquement tous vos problèmes de message de validation et les problèmes de soumission de formulaire / de liaison de modèle.
la source
Theme
modèle ne serait pas hydratée correctement.Lorsque vous utilisez la boucle foreach dans la vue pour le modèle lié ... Votre modèle est censé être au format répertorié.
c'est à dire
la source
Cela ressort clairement de l'erreur.
Le HtmlHelpers ajouté avec "For" attend une expression lambda comme paramètre.
Si vous passez la valeur directement, mieux vaut utiliser une valeur normale.
par exemple
Au lieu de TextboxFor (....), utilisez Textbox ()
la syntaxe de TextboxFor sera comme Html.TextBoxFor (m => m.Property)
Dans votre scénario, vous pouvez utiliser la boucle for de base, car elle vous donnera un index à utiliser.
la source
Une autre possibilité beaucoup plus simple est que l'un de vos noms de propriété est erroné (probablement celui que vous venez de changer dans la classe). C'est ce que c'était pour moi dans RazorPages .NET Core 3.
la source