Die schnelle Antwort besteht darin, for()
anstelle Ihrer foreach()
Schleifen eine Schleife zu verwenden . Etwas wie:
@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)
}
}
}
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:
- Wie arbeiten die
LabelFor
und andere ...For
Helfer in MVC?
- Was ist ein Ausdrucksbaum?
- Wie funktioniert der Model Binder?
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ür LabelFor
und TextBoxFor
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
public static MvcHtmlString TextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
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 von HtmlHelper<TModel>
(als Eigenschaft Html
, weshalb Sie sie verwenden können @Html...
), wobei TModel
der in Ihrer @model
Anweisung definierte Typ ist . Wenn Sie also in diesem Fall diese Ansicht betrachten, ist TModel
sie immer vom Typ ViewModels.MyViewModels.Theme
.
Das nächste Argument ist etwas knifflig. Schauen wir uns also einen Aufruf an
@Html.TextBoxFor(model=>model.SomeProperty);
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>
, wobei TModel
der Typ des Ansichtsmodells ist und TProperty
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!
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<>
oder Action<>
, sobald Sie sie haben, sind sie ziemlich atomar. Alles, was Sie wirklich tun können, sind Invoke()
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ückkehren Html.TextBoxFor
. Wenn ein Textfeld gerendert wird, müssen einige Dinge über die Eigenschaft generiert werden , die Sie ihm geben. Dinge wie attributes
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
Foo.Bar.Baz.FooBar = Hello
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:
@Html.TextBoxFor(model=>model.FooBar)
Eher, als
@Html.TextBoxFor(model=>model.Foo.Bar.Baz.FooBar)
Das bedeutet, dass ein Eingabe-Tag wie folgt generiert wird:
<input name="FooBar" />
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
Off TModel
. Was im besten Fall nicht da ist und im schlimmsten Fall etwas ganz anderes. Wenn Sie zu einer bestimmten Aktion posten würden, die ein Baz
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.
@
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?).