renderpartial z modelem zerowym przechodzi niepoprawny typ

198

Mam stronę:

<%@ Page Inherits="System.Web.Mvc.View<DTOSearchResults>" %>

I na tym następujące:

<% Html.RenderPartial("TaskList", Model.Tasks); %>

Oto obiekt DTO:

public class DTOSearchResults
{
    public string SearchTerm { get; set; }
    public IEnumerable<Task> Tasks { get; set; }

a oto częściowe:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Task>>" %>

Gdy Model.Tasks nie ma wartości NULL, wszystko działa dobrze. Jednak gdy jest zerowy, otrzymuję:

Element modelu przekazany do słownika jest typu „DTOSearchResults”, ale ten słownik wymaga elementu modelu typu „System.Collections.Generic.IEnumerable`1 [Zadanie]”.

Uznałem, że nie może wiedzieć, które przeciążenie użyć, więc zrobiłem to (patrz poniżej), aby być jawnym, ale wciąż mam ten sam problem!

<% Html.RenderPartial("TaskList", (object)Model.Tasks, null); %>

Wiem, że mogę to obejść, sprawdzając wartość null lub nawet nie przekazując wartości null, ale nie o to chodzi. Dlaczego to się dzieje?

Andrew Bullock
źródło

Odpowiedzi:

349

Andrew Myślę, że problem, który otrzymujesz, jest wynikiem metody RenderPartial wykorzystującej model wywołujący (widok) do widoku częściowego, gdy przekazywany model ma wartość zerową .. możesz obejść to dziwne zachowanie, wykonując:

<% Html.RenderPartial("TaskList", Model.Tasks, new ViewDataDictionary()); %>

To pomaga?

meandmycode
źródło
16
Nadal oszczędzam ludziom czas. Ciągnęłam za to włosy.
James Gregory,
3
Rozumiem, dlaczego obsługują model zerowy i przekazują model stron, ale nie mogli sobie z tym poradzić przez przeciążenie. @ Html.Render („osły”) różni się od @ Html.Render („osły”, canbenull)
Phil Strong
19
Uważam to za bardzo sprzeczne z intuicją, więc dodałem „problem”, zagłosuj na nie, jeśli się zgodzisz: aspnet.codeplex.com/workitem/8872
pbz
3
Odkryłem, że dzięki temu rozwiązaniu moje ValidationSummary w moim widoku częściowym nie działało, ponieważ ViewData modelu podstawowego zostało utracone w widoku częściowym. Użyłem odpowiedzi podanej tutaj stackoverflow.com/a/12037580/649497, aby rozwiązać ten problem.
BruceHill
5
Powinieneś przekazać istniejące ViewData: nowe ViewDataDictionary (ViewData)
ScottE
48

@ Odpowiedź myandmycode jest dobra, ale nieco krótsza

<% Html.RenderPartial("TaskList", new ViewDataDictionary(Model.Tasks)); %>

Działa to, ponieważ to ViewDataDictionarywłaśnie ono utrzymuje model i może zaakceptować model jako parametr konstruktora. Zasadniczo przekazuje to „cały” słownik danych widoku, który oczywiście zawiera tylko model możliwie zerowy.

konfigurator
źródło
2
@jcmcbeth: Ehm, nie, nie robi ... Z powodzeniem użyłem tego kodu z zerami.
konfigurator
1
@jcmcbeth: Używasz new ViewDataDictionary(null)? Ponieważ wybrałoby to inne przeciążenie, z ViewDataDictionaryparametrem, który prawdopodobnie nie zaakceptowałby wartości zerowych.
konfigurator
1
Wygląda na to, że użycie właściwości ViewBag powoduje wywołanie niewłaściwego konstruktora. To, jak przyjmuje typ dynamiczny i zakłada, że ​​jest to obiekt ViewDataDictionary nad obiektem, nie ma dla mnie sensu, ale wydaje się, że to właśnie robi. Będziesz musiał rzucić go na obiekt, aby mógł wybrać prawidłowego konstruktora.
Joel McBeth
1
@jcmcbeth: Wywołanie go za pomocą typu dynamicznego wykorzystuje to samo, co w przypadku podania rzeczywistej wartości; jeśli wartość jest nulltaka sama jak wywołanie, new ViewDataDictionary(null)które powoduje wywołanie najbardziej specyficznego przeciążenia.
konfigurator
1
jeśli użyjesz go w ten sposób, błąd słownika zniknie. Html.RenderPartial("TaskList", new ViewDataDictionary(model: Model.Tasks))Używasz niewłaściwego konstruktora, jeśli jest pusty.
Filip Cornelissen
26

Wygląda na to, że gdy właściwość przekazywanego modelu ma wartość NULL, MVC celowo przywraca model „nadrzędny”. Najwyraźniej silnik MVC interpretuje wartość modelu zerowego jako zamiar użycia poprzedniej.

Nieco więcej szczegółów tutaj: ASP.NET MVC, mocno wpisane widoki, częściowe parametry wyświetlania usterki

Zack
źródło
1
+1 za próbę wyjaśnienia problemu, a nie traktowanie tego jako dziwnego zachowania
YavgenyP
Tak, to się przydarzyło i powyższe nie naprawiło tego, tylko dało mi trochę więcej informacji na temat mojego rzeczywistego błędu.
Canvas
20

Jeśli nie chcesz stracić poprzednich ViewData w widoku częściowym, możesz spróbować:

<% Html.RenderPartial("TaskList", Model.Tasks, new ViewDataDictionary(ViewData){Model = null});%>
Fran P.
źródło
1
To nie wydaje się odpowiadać na pytanie.
John Saunders,
6
+1 Właściwie to działa. Jest to w zasadzie ten sam pomysł przedstawiony tutaj stackoverflow.com/a/713921/649497, ale przezwycięża problem z tą odpowiedzią, a mianowicie, że ViewData zniknie, jeśli utworzysz instancję ViewDataDictionary z pustym konstruktorem. Najpierw rozwiązałem ten problem za pomocą przyjętego rozwiązania, a następnie stwierdziłem, że moje ValidationSummary nie działa w widoku częściowym. To rozwiązanie rozwiązało to dla mnie. Ta odpowiedź wymaga większego rozpoznania w celu rozwiązania problemu i zachowania ViewData w widoku częściowym.
BruceHill
1
@Franc P to faktycznie działało bez utraty wartości ViewBag, a zatem przeszło model zerowy. Dzięki.
Zaker
To dobra odpowiedź, jeśli potrzebujesz dostępu do ViewBag w swoich częściach!
Daniel Lorenz
12

Rozwiązaniem byłoby utworzenie HtmlHelper w następujący sposób:

public static MvcHtmlString Partial<T>(this HtmlHelper htmlHelper, string partialViewName, T model)
{
    ViewDataDictionary viewData = new ViewDataDictionary(htmlHelper.ViewData)
    {
        Model = model
    };
    return PartialExtensions.Partial(htmlHelper, partialViewName, model, viewData);
}

Partial<T>(...)Dopasowane przed Partial(...)tak wygodny i bez błędu niejednoznaczności podczas kompilacji.

Osobiście trudno mi zrozumieć zachowanie - wydaje się, że trudno to sobie wyobrazić jako wybór projektu?

Colin Breame
źródło
1
to właśnie zrobiłem w końcu. w asp.net mvc nie ma wielu wyborów / zachowań projektowych, co ma jakikolwiek sens. odkąd to porzuciłem. pomocny dla innych, więc miej +1
Andrew Bullock
Dobry, ale dla użytkownika niejasny. Powiedzmy, że jestem przyzwyczajony do tego, co mój kolega wykorzystuje w swoim projekcie, zaczynam nowy. Potem całkowicie zapomnij dodać to przeciążenie i voilla, wyjątki zaczynają się pojawiać w produkcji, ponieważ nie przetestowaliśmy tego wystarczająco dobrze. Inna nazwa to Beter Imho.
Jaap
11

Chociaż udzielono na to odpowiedzi, natknąłem się na to i zdecydowałem, że chcę rozwiązać ten problem w moim projekcie, zamiast z nim pracować new ViewDataDictionary().

Stworzyłem zestaw metod rozszerzeń: https://github.com/q42jaap/PartialMagic.Mvc/blob/master/PartialMagic.Mvc/PartialExtensions.cs
Dodałem także kilka metod, które nie wywołują częściowego, jeśli model ma wartość null , zaoszczędzi to wiele instrukcji if.

Stworzyłem je dla Razor, ale kilka z nich powinno również działać z widokami w stylu aspx (te, które używają HelperResult prawdopodobnie nie są kompatybilne).

Metody rozszerzenia wyglądają następująco:

@* calls the partial with Model = null *@
@Html.PartialOrNull("PartialName", null)
@* does not call the partial if the model is null *@
@Html.PartialOrDiscard("PartialName", null)

Istnieją również metody dla IEnumerable<object>modeli, a te odrzucone można również wywoływać za pomocą lambda Razor, które pozwalają owinąć częściowy wynik pewnym html.

Możesz je wykorzystać, jeśli chcesz.

Jaap
źródło
1
Nadal przydatne od MVC5: 25.6.2014. Dzięki.
Jason
1

Moje obejście tego jest następujące:


<% Html.RenderPartial("TaskList", Model.Tasks ?? new List()); %>

h3n
źródło
To brudne rozwiązanie. W widoku częściowym powinieneś być w stanie sprawdzić, czy model ma wartość null, zamiast sprawdzać, czy lista ma jakieś wartości i czy jest null.
madd