Wyobraź sobie typowy scenariusz, to prostsza wersja tego, z czym się spotykam. W rzeczywistości mam kilka warstw dalszego zagnieżdżenia na moim ...
Ale taki jest scenariusz
Motyw zawiera listę Kategoria zawiera listę Produkt zawiera listę
Mój kontroler zapewnia w pełni wypełniony motyw ze wszystkimi kategoriami dla tego motywu, produktami w tych kategoriach i ich zamówieniami.
Kolekcja zamówień ma właściwość o nazwie Ilość (między innymi), którą należy edytować.
@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)
}
}
}
Jeśli zamiast tego użyję lambdy, to wydaje mi się, że otrzymuję tylko odniesienie do najwyższego obiektu Model, „Theme”, a nie tych w pętli foreach.
Czy to, co próbuję tam zrobić, jest w ogóle możliwe, czy też przeszacowałem lub źle zrozumiałem, co jest możliwe?
W związku z powyższym otrzymuję błąd w TextboxFor, EditorFor itp
CS0411: Na podstawie użycia nie można wywnioskować argumentów typu metody „System.Web.Mvc.Html.InputExtensions.TextBoxFor (System.Web.Mvc.HtmlHelper, System.Linq.Expressions.Expression>)”. Spróbuj jawnie określić argumenty typu.
Dzięki.
@
przed wszystkimforeach
? Czy nie powinieneś również mieć lambd wHtml.EditorFor
(Html.EditorFor(m => m.Note)
na przykład) i pozostałych metodach? Mogę się mylić, ale czy możesz wkleić swój rzeczywisty kod? Jestem całkiem nowy w MVC, ale można go dość łatwo rozwiązać za pomocą częściowych widoków lub edytorów (jeśli tak się nazywają?).category.name
Jestem pewien, że jest tostring
i...For
nie obsługuje łańcucha jako pierwszego parametru:)
.for()
zamiast plikuforeach
. Wytłumaczę dlaczego, bo przez długi czas też mnie to zdezorientowało.Odpowiedzi:
Szybką odpowiedzią jest użycie
for()
pętli zamiastforeach()
pętli. Coś jak:Ale to wyjaśnia, dlaczego to rozwiązuje problem.
Są trzy rzeczy, które musisz przynajmniej pobieżnie zrozumieć, zanim będziesz mógł rozwiązać ten problem. Muszę przyznać, że bardzo długo to kultywowałem, kiedy zacząłem pracować z frameworkiem. Zajęło mi trochę czasu, zanim naprawdę zrozumiałem, co się dzieje.
Te trzy rzeczy to:
LabelFor
i inni...For
pomocnicy w MVC?Wszystkie trzy pojęcia łączą się, aby uzyskać odpowiedź.
Jak działają pomocnicy
LabelFor
i inni...For
pomocnicy w MVC?Więc użyłeś
HtmlHelper<T>
rozszerzeń dlaLabelFor
iTextBoxFor
i innych, i prawdopodobnie zauważyłeś, że kiedy je wywołujesz, przekazujesz im lambdę i magicznie generuje jakiś kod HTML. Ale jak?Więc pierwszą rzeczą, na którą należy zwrócić uwagę, jest podpis tych pomocników. Spójrzmy na najprostsze przeciążenie dla
TextBoxFor
Po pierwsze, jest to metoda rozszerzająca dla silnie wpisanego
HtmlHelper
typu<TModel>
. Tak więc, aby po prostu stwierdzić, co dzieje się za kulisami, kiedy brzytwa renderuje ten widok, generuje klasę. Wewnątrz tej klasy znajduje się instancjaHtmlHelper<TModel>
(jako właściwośćHtml
, dlatego możesz użyć@Html...
), gdzieTModel
jest typem zdefiniowanym w@model
instrukcji. Więc w twoim przypadku, kiedy patrzysz na ten widokTModel
, zawsze będzie to typViewModels.MyViewModels.Theme
.Teraz następny argument jest nieco skomplikowany. Spójrzmy więc na inwokację
Wygląda na to, że mamy małą lambdę. Gdyby zgadnąć sygnaturę, można by pomyśleć, że typem tego argumentu będzie po prostu a
Func<TModel, TProperty>
, gdzieTModel
jest typem modelu widoku iTProperty
jest wywnioskowany jako typ właściwości.Ale to nie do końca w porządku, jeśli spojrzeć na rzeczywisty typ argumentu its
Expression<Func<TModel, TProperty>>
.Więc kiedy normalnie generujesz lambdę, kompilator pobiera lambdę i kompiluje ją do MSIL, tak jak każda inna funkcja (dlatego możesz używać delegatów, grup metod i lambd mniej lub bardziej zamiennie, ponieważ są one tylko odwołaniami do kodu .)
Jednak gdy kompilator widzi, że typ to an
Expression<>
, nie kompiluje natychmiast lambda do MSIL, zamiast tego generuje drzewo wyrażeń!Co to jest drzewo wyrażeń ?
A więc, do cholery, jest drzewo ekspresji. Cóż, to nie jest skomplikowane, ale nie jest to też spacer po parku. Cytując ms:
| Drzewa wyrażeń reprezentują kod w strukturze danych podobnej do drzewa, gdzie każdy węzeł jest wyrażeniem, na przykład wywołaniem metody lub operacją binarną, taką jak x <y.
Mówiąc najprościej, drzewo wyrażeń jest reprezentacją funkcji jako zbiór „akcji”.
W przypadku
model=>model.SomeProperty
drzewa wyrażenia byłoby w nim węzeł, który mówi: „Pobierz 'jakąś właściwość' z 'modelu'”To drzewo wyrażeń można skompilować w funkcję, którą można wywołać, ale dopóki jest to drzewo wyrażeń, jest to po prostu zbiór węzłów.
Więc po co to jest dobre?
Więc
Func<>
alboAction<>
kiedy już je masz, są prawie atomowe. Wszystko, co naprawdę możesz zrobić, toInvoke()
ich, czyli powiedzieć im, aby wykonali pracę, którą powinni wykonać.Expression<Func<>>
z drugiej strony reprezentuje zbiór działań, które mogą być dołączane, manipulowane, odwiedzane lub kompilowane i wywoływane.Więc dlaczego mi to wszystko mówisz?
Więc mając zrozumienie tego, czym
Expression<>
jest, możemy wrócić doHtml.TextBoxFor
. Kiedy renderuje pole tekstowe, musi wygenerować kilka informacji o właściwości, którą mu nadajesz. Rzeczy takie jakattributes
na nieruchomości dla walidacji, a konkretnie w tym przypadku musi dowiedzieć się, co nazwać ten<input>
tag.Odbywa się to poprzez „chodzenie” po drzewie wyrażeń i budowanie nazwy. Tak więc w przypadku wyrażenia typu
model=>model.SomeProperty
przechodzi przez wyrażenie, zbierając właściwości, o które prosisz i które tworzy<input name='SomeProperty'>
.Dla bardziej skomplikowanego przykładu,
model=>model.Foo.Bar.Baz.FooBar
może wygenerować<input name="Foo.Bar.Baz.FooBar" value="[whatever FooBar is]" />
Ma sens? Nie tylko praca, którą
Func<>
wykonuje, ale to, jak wykonuje swoją pracę, jest tutaj ważne.(Zwróć uwagę, że inne struktury, takie jak LINQ to SQL, robią podobne rzeczy, chodząc po drzewie wyrażeń i budując inną gramatykę, w tym przypadku zapytanie SQL)
Jak działa segregator modeli?
Więc kiedy już to zrozumiesz, musimy krótko porozmawiać o segregatorze modelowym. Kiedy formularz jest wysyłany, jest po prostu jak płaski
Dictionary<string, string>
, straciliśmy strukturę hierarchiczną, którą mógł mieć nasz zagnieżdżony model widoku. Zadaniem segregatora modelu jest pobranie tej kombinacji klucz-wartość i próba ponownego uwodnienia obiektu z pewnymi właściwościami. Jak to się dzieje? Zgadłeś, używając „klucza” lub nazwy opublikowanego wejścia.Więc jeśli formularz wygląda jak post
Piszesz do modelu o nazwie
SomeViewModel
, a następnie robi to odwrotnie niż w pierwszej kolejności. Szuka właściwości o nazwie „Foo”. Następnie szuka właściwości o nazwie „Bar” poza „Foo”, potem wyszukuje „Baz”… i tak dalej…W końcu próbuje przeanalizować wartość do typu „FooBar” i przypisać ją do „FooBar”.
PHEW !!!
I voila, masz swój model. Wystąpienie, które właśnie skonstruowano Model Binder, zostaje przekazane do żądanej akcji.
Twoje rozwiązanie nie działa, ponieważ
Html.[Type]For()
pomocnicy potrzebują wyrażenia. A ty po prostu nadajesz im wartość. Nie ma pojęcia, jaki jest kontekst tej wartości i nie wie, co z nią zrobić.Teraz niektórzy zasugerowali użycie podszablonów do renderowania. Teoretycznie to zadziała, ale prawdopodobnie nie tak, jak się spodziewasz. Kiedy renderujesz częściową, zmieniasz typ
TModel
, ponieważ znajdujesz się w innym kontekście widoku. Oznacza to, że możesz opisać swoją nieruchomość za pomocą krótszego wyrażenia. Oznacza to również, że kiedy pomocnik wygeneruje nazwę dla twojego wyrażenia, będzie ono płytkie. Generuje się tylko na podstawie podanego wyrażenia (nie całego kontekstu).Powiedzmy, że masz podrzędny fragment, który właśnie wyrenderował „Baz” (z naszego przykładu wcześniej). Wewnątrz tej części możesz po prostu powiedzieć:
Zamiast
Oznacza to, że wygeneruje taki tag wejściowy:
Które, jeśli Piszesz tego formularza do działania, że spodziewa się dużego głęboko zagnieżdżony ViewModel, a następnie spróbuje nawilżają właściwość o nazwie
FooBar
offTModel
. Czego w najlepszym razie nie ma, aw najgorszym jest czymś zupełnie innym. Gdybyś wysyłał do określonej akcji, która akceptowała modelBaz
, a nie model główny, to działałoby świetnie! W rzeczywistości częściowe są dobrym sposobem na zmianę kontekstu widoku, na przykład jeśli masz stronę z wieloma formularzami, które wszystkie publikują w różnych działaniach, renderowanie części dla każdego z nich byłoby świetnym pomysłem.Teraz, gdy już to wszystko osiągniesz, możesz zacząć robić naprawdę interesujące rzeczy
Expression<>
, programowo je rozszerzając i robiąc z nimi inne fajne rzeczy. Nie będę się tym zajmować. Ale miejmy nadzieję, że da ci to lepsze zrozumienie tego, co dzieje się za kulisami i dlaczego rzeczy działają tak, jak są.źródło
Możesz po prostu użyć EditorTemplates, aby to zrobić, musisz utworzyć katalog o nazwie „EditorTemplates” w folderze widoku kontrolera i umieścić oddzielny widok dla każdej zagnieżdżonej encji (nazwanej jako nazwa klasy encji)
Główny widok :
Widok kategorii (/MyController/EditorTemplates/Category.cshtml):
Widok produktu (/MyController/EditorTemplates/Product.cshtml):
i tak dalej
w ten sposób Html.EditorFor helper wygeneruje nazwy elementów w uporządkowany sposób, dzięki czemu nie będziesz mieć żadnego problemu z odzyskaniem opublikowanej encji Theme jako całości
źródło
Możesz dodać częściową kategorię i część produktu, każdy z nich zająłby mniejszą część modelu głównego, ponieważ jest to własny model, tj. Typem modelu kategorii może być IEnumerable, należy przekazać do niego Model.Theme. Częścią produktu może być IEnumerable, do którego przekazujesz Model.Products (z części częściowej Category).
Nie jestem pewien, czy byłaby to właściwa droga naprzód, ale chciałbym wiedzieć.
EDYTOWAĆ
Od opublikowania tej odpowiedzi korzystam z EditorTemplates i uważam, że jest to najłatwiejszy sposób obsługi powtarzających się grup lub elementów wejściowych. Obsługuje wszystkie problemy z wiadomościami walidacyjnymi i automatycznie zgłasza problemy z przesłaniem formularza / wiązaniem modelu.
źródło
Theme
modelu nie byłoby odpowiednio nawodnione.Gdy używasz pętli foreach w widoku dla modelu powiązanego ... Twój model powinien być w formacie listy.
to znaczy
źródło
Wynika to jasno z błędu.
HtmlHelpers dołączony z „For” oczekuje wyrażenia lambda jako parametru.
Jeśli przekazujesz wartość bezpośrednio, lepiej użyj normalnej.
na przykład
Zamiast TextboxFor (....) użyj Textbox ()
składnia TextboxFor będzie wyglądać jak Html.TextBoxFor (m => m.Property)
W swoim scenariuszu możesz użyć podstawowej pętli for, ponieważ da ci ona indeks do użycia.
źródło
Inną znacznie prostszą możliwością jest to, że jedna z nazw właściwości jest nieprawidłowa (prawdopodobnie ta, którą właśnie zmieniłeś w klasie). Tak właśnie było dla mnie w RazorPages .NET Core 3.
źródło