Stellen Sie sich ein häufiges Szenario vor. Dies ist eine einfachere Version dessen, was mir begegnet. Ich habe tatsächlich ein paar weitere Nistschichten auf meiner ...
Dies ist jedoch das Szenario
Thema enthält Liste Kategorie enthält Liste Produkt enthält Liste
Mein Controller bietet ein vollständig ausgefülltes Thema mit allen Kategorien für dieses Thema, den Produkten in diesen Kategorien und deren Bestellungen.
Die Auftragssammlung verfügt über eine Eigenschaft namens Menge (unter anderem), die bearbeitet werden muss.
@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)
}
}
}
Wenn ich stattdessen Lambda verwende, bekomme ich anscheinend nur einen Verweis auf das oberste Modellobjekt, "Theme", nicht auf diejenigen innerhalb der foreach-Schleife.
Ist das, was ich dort versuche, überhaupt möglich oder habe ich überschätzt oder missverstanden, was möglich ist?
Mit dem oben genannten erhalte ich eine Fehlermeldung bei TextboxFor, EditorFor usw.
CS0411: Die Typargumente für die Methode 'System.Web.Mvc.Html.InputExtensions.TextBoxFor (System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>)' können aus der Verwendung nicht abgeleitet werden. Versuchen Sie, die Typargumente explizit anzugeben.
Vielen Dank.
@
vor allemforeach
s haben? Sollten Sie nicht auch LambdasHtml.EditorFor
(Html.EditorFor(m => m.Note)
zum Beispiel) und den Rest der Methoden haben? Ich kann mich irren, aber können Sie bitte Ihren tatsächlichen Code einfügen? Ich bin ziemlich neu in MVC, aber Sie können es ziemlich einfach mit Teilansichten oder Editoren lösen (wenn das der Name ist?).category.name
Ich bin sicher, es ist einstring
und...For
unterstützt keinen String als ersten Parameter:)
.for()
anstelle eines zu verwendenforeach
. Ich werde erklären, warum, weil es mich auch lange Zeit verdammt verwirrt hat.Antworten:
Die schnelle Antwort besteht darin,
for()
anstelle Ihrerforeach()
Schleifen eine Schleife zu verwenden . Etwas wie:Dies beschönigt jedoch, warum dies das Problem behebt.
Es gibt drei Dinge, die Sie zumindest flüchtig verstehen müssen, bevor Sie dieses Problem beheben können. Ich muss zugeben, dass ich dies lange Zeit mit Fracht kultiviert habe, als ich anfing, mit dem Framework zu arbeiten. Und ich habe eine ganze Weile gebraucht, um wirklich zu verstehen, was los war.
Diese drei Dinge sind:
LabelFor
und andere...For
Helfer in MVC?Alle drei Konzepte verbinden sich, um eine Antwort zu erhalten.
Wie arbeiten die
LabelFor
und andere...For
Helfer in MVC?Sie haben also die
HtmlHelper<T>
Erweiterungen fürLabelFor
undTextBoxFor
und andere verwendet, und Sie haben wahrscheinlich bemerkt, dass Sie ihnen beim Aufrufen ein Lambda übergeben und auf magische Weise HTML- Code generieren. Aber wie?Das erste, was zu bemerken ist, ist die Unterschrift für diese Helfer. Schauen wir uns die einfachste Überlastung an
TextBoxFor
Erstens ist dies eine Erweiterungsmethode für einen stark typisierten
HtmlHelper
Typ<TModel>
. Um einfach zu sagen, was hinter den Kulissen passiert: Wenn Razor diese Ansicht rendert, wird eine Klasse generiert. Innerhalb dieser Klasse befindet sich eine Instanz vonHtmlHelper<TModel>
(als EigenschaftHtml
, weshalb Sie sie verwenden können@Html...
), wobeiTModel
der in Ihrer@model
Anweisung definierte Typ ist . Wenn Sie also in diesem Fall diese Ansicht betrachten, istTModel
sie immer vom TypViewModels.MyViewModels.Theme
.Das nächste Argument ist etwas knifflig. Schauen wir uns also einen Aufruf an
Es sieht so aus, als hätten wir ein kleines Lambda. Und wenn man die Signatur erraten würde, könnte man denken, dass der Typ für dieses Argument einfach a ist
Func<TModel, TProperty>
, wobeiTModel
der Typ des Ansichtsmodells ist undTProperty
als Typ der Eigenschaft abgeleitet wird.Aber das ist nicht ganz richtig, wenn man sich den tatsächlichen Typ des Arguments ansieht
Expression<Func<TModel, TProperty>>
.Wenn Sie also normalerweise ein Lambda generieren, nimmt der Compiler das Lambda und kompiliert es wie jede andere Funktion in MSIL (weshalb Sie Delegaten, Methodengruppen und Lambdas mehr oder weniger austauschbar verwenden können, da es sich nur um Code-Referenzen handelt .)
Wenn der Compiler
Expression<>
jedoch erkennt, dass es sich um einen Typ handelt , kompiliert er das Lambda nicht sofort in MSIL, sondern generiert einen Ausdrucksbaum!Was ist ein Ausdrucksbaum ?
Also, was zum Teufel ist ein Ausdrucksbaum. Nun, es ist nicht kompliziert, aber es ist auch kein Spaziergang im Park. Um ms zu zitieren:
| Ausdrucksbäume stellen Code in einer baumartigen Datenstruktur dar, wobei jeder Knoten ein Ausdruck ist, beispielsweise ein Methodenaufruf oder eine binäre Operation wie x <y.
Einfach ausgedrückt ist ein Ausdrucksbaum eine Darstellung einer Funktion als Sammlung von "Aktionen".
Im Fall von
model=>model.SomeProperty
würde der Ausdrucksbaum einen Knoten enthalten, der besagt: "Holen Sie sich 'Some Property' von einem 'Modell'"Dieser Ausdrucksbaum kann zu einer Funktion kompiliert werden, die aufgerufen werden kann. Solange es sich jedoch um einen Ausdrucksbaum handelt, handelt es sich nur um eine Sammlung von Knoten.
Wofür ist das gut?
Also
Func<>
oderAction<>
, sobald Sie sie haben, sind sie ziemlich atomar. Alles, was Sie wirklich tun können, sindInvoke()
sie, auch bekannt als sagen Sie ihnen, dass sie die Arbeit tun sollen, die sie tun sollen.Expression<Func<>>
Auf der anderen Seite handelt es sich um eine Sammlung von Aktionen, die angehängt, bearbeitet, besucht oder kompiliert und aufgerufen werden können.Warum erzählst du mir das alles?
Mit diesem Verständnis dessen, was ein
Expression<>
ist, können wir zurückkehrenHtml.TextBoxFor
. Wenn ein Textfeld gerendert wird, müssen einige Dinge über die Eigenschaft generiert werden , die Sie ihm geben. Dinge wieattributes
auf der Eigenschaft zur Validierung, und speziell in diesem Fall muss es herausfinden, wie das Tag zu benennen ist<input>
.Dies geschieht durch "Gehen" des Ausdrucksbaums und Erstellen eines Namens. Für einen Ausdruck wie
model=>model.SomeProperty
geht es also um den Ausdruck, der die Eigenschaften sammelt, nach denen Sie fragen, und erstellt<input name='SomeProperty'>
.Für ein komplizierteres Beispiel
model=>model.Foo.Bar.Baz.FooBar
könnte es generiert werden<input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />
Sinn ergeben? Es ist nicht nur die Arbeit, die das
Func<>
macht, sondern auch, wie es seine Arbeit macht, ist hier wichtig.(Beachten Sie, dass andere Frameworks wie LINQ to SQL ähnliche Aktionen ausführen, indem sie einen Ausdrucksbaum durchlaufen und eine andere Grammatik erstellen, in diesem Fall eine SQL-Abfrage.)
Wie funktioniert der Model Binder?
Sobald Sie das bekommen, müssen wir kurz über den Modellbinder sprechen. Wenn das Formular veröffentlicht wird, ist es einfach wie eine Wohnung
Dictionary<string, string>
. Wir haben die hierarchische Struktur verloren, die unser verschachteltes Ansichtsmodell möglicherweise hatte. Es ist die Aufgabe des Modellbinders, diese Schlüssel-Wert-Paar-Kombination zu verwenden und zu versuchen, ein Objekt mit einigen Eigenschaften zu rehydrieren. Wie macht es das? Sie haben es erraten, indem Sie den "Schlüssel" oder den Namen der Eingabe verwendet haben, die veröffentlicht wurde.Also, wenn der Formularbeitrag so aussieht
Und Sie posten auf einem Modell namens
SomeViewModel
, dann macht es das Gegenteil von dem, was der Helfer zuerst getan hat. Es sucht nach einer Eigenschaft namens "Foo". Dann sucht es nach einer Eigenschaft namens "Bar" von "Foo", dann sucht es nach "Baz" ... und so weiter ...Schließlich wird versucht, den Wert in den Typ "FooBar" zu analysieren und ihn "FooBar" zuzuweisen.
PUH!!!
Und voila, du hast dein Modell. Die Instanz, die der Modellbinder gerade erstellt hat, wird in die angeforderte Aktion übergeben.
Ihre Lösung funktioniert also nicht, weil die
Html.[Type]For()
Helfer einen Ausdruck benötigen. Und du gibst ihnen nur einen Wert. Es hat keine Ahnung, was der Kontext für diesen Wert ist, und es weiß nicht, was es damit anfangen soll.Nun schlugen einige Leute vor, Partials zum Rendern zu verwenden. Nun wird dies theoretisch funktionieren, aber wahrscheinlich nicht so, wie Sie es erwarten. Wenn Sie einen Teil rendern, ändern Sie den Typ von
TModel
, da Sie sich in einem anderen Ansichtskontext befinden. Dies bedeutet, dass Sie Ihre Immobilie mit einem kürzeren Ausdruck beschreiben können. Dies bedeutet auch, dass der Helfer, wenn er den Namen für Ihren Ausdruck generiert, flach ist. Es wird nur basierend auf dem angegebenen Ausdruck generiert (nicht der gesamte Kontext).Nehmen wir also an, Sie hatten einen Teil, der gerade "Baz" gerendert hat (aus unserem vorherigen Beispiel). In diesem Teil könnte man einfach sagen:
Eher, als
Das bedeutet, dass ein Eingabe-Tag wie folgt generiert wird:
Welche, wenn Sie dieses Formular auf eine Aktion Mitteilung verfassen , die eine große tief verschachtelt Ansichtsmodell erwartet, dann wird es versuchen , eine Eigenschaft , um Hydrat genannt
FooBar
OffTModel
. Was im besten Fall nicht da ist und im schlimmsten Fall etwas ganz anderes. Wenn Sie zu einer bestimmten Aktion posten würden, die einBaz
anstelle des Stammmodells akzeptiert , würde dies hervorragend funktionieren! Partials sind in der Tat eine gute Möglichkeit, Ihren Ansichtskontext zu ändern. Wenn Sie beispielsweise eine Seite mit mehreren Formularen haben, die alle unterschiedliche Aktionen ausführen, ist es eine gute Idee, für jedes eine Partial zu rendern.Sobald Sie all dies erhalten haben, können Sie anfangen, wirklich interessante Dinge zu tun
Expression<>
, indem Sie sie programmgesteuert erweitern und andere nette Dinge mit ihnen tun. Darauf werde ich nicht eingehen. Aber hoffentlich erhalten Sie dadurch ein besseres Verständnis dafür, was sich hinter den Kulissen abspielt und warum sich die Dinge so verhalten, wie sie sind.quelle
Sie können dazu einfach EditorTemplates verwenden. Sie müssen ein Verzeichnis mit dem Namen "EditorTemplates" im Ansichtsordner Ihres Controllers erstellen und eine separate Ansicht für jede Ihrer verschachtelten Entitäten platzieren (als Entitätsklassenname bezeichnet).
Hauptansicht :
Kategorieansicht (/MyController/EditorTemplates/Category.cshtml):
Produktansicht (/MyController/EditorTemplates/Product.cshtml):
und so weiter
Auf diese Weise generiert der Html.EditorFor-Helfer die Namen der Elemente in geordneter Weise, sodass Sie keine weiteren Probleme beim Abrufen der veröffentlichten Theme-Entität als Ganzes haben
quelle
Sie können einen Kategorieteil und einen Produktteil hinzufügen, wobei jeder einen kleineren Teil des Hauptmodells als eigenes Modell übernimmt. Der Modelltyp der Kategorie kann also eine IE-Zahl sein, die Sie in Model.Theme übergeben. Der Teil des Produkts kann ein IEnumerable sein, an den Sie Model.Products übergeben (aus dem Teil der Kategorie heraus).
Ich bin mir nicht sicher, ob das der richtige Weg wäre, würde mich aber dafür interessieren.
BEARBEITEN
Seit ich diese Antwort gepostet habe, habe ich EditorTemplates verwendet und finde, dass dies der einfachste Weg ist, um sich wiederholende Eingabegruppen oder Elemente zu verarbeiten. Es behandelt alle Probleme mit Validierungsnachrichten und Probleme mit der Übermittlung von Formularen / Modellbindungen automatisch.
quelle
Theme
Modell würde nicht richtig hydratisiert.Wenn Sie eine foreach-Schleife in der Ansicht für ein gebundenes Modell verwenden ... Ihr Modell sollte im aufgelisteten Format vorliegen.
dh
quelle
Es ist aus dem Fehler klar.
Die mit "For" angehängten HtmlHelpers erwarten einen Lambda-Ausdruck als Parameter.
Wenn Sie den Wert direkt übergeben, verwenden Sie besser Normal.
z.B
Verwenden Sie anstelle von TextboxFor (....) Textbox ()
Die Syntax für TextboxFor lautet wie folgt: Html.TextBoxFor (m => m.Property)
In Ihrem Szenario können Sie basic for loop verwenden, da Sie dadurch einen Index zur Verwendung erhalten.
quelle
Eine andere viel einfachere Möglichkeit ist, dass einer Ihrer Eigenschaftsnamen falsch ist (wahrscheinlich einer, den Sie gerade in der Klasse geändert haben). Das war es für mich in RazorPages .NET Core 3.
quelle