Renderpartial mit Nullmodell wird vom falschen Typ übergeben

198

Ich habe eine Seite:

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

Und darauf folgendes:

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

Hier ist das DTO-Objekt:

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

und hier ist der Teil:

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

Wenn Model.Tasks nicht null ist, funktioniert alles einwandfrei. Wenn es jedoch null ist, bekomme ich:

Das an das Wörterbuch übergebene Modellelement ist vom Typ 'DTOSearchResults', für dieses Wörterbuch ist jedoch ein Modellelement vom Typ 'System.Collections.Generic.IEnumerable`1 [Task]' erforderlich.

Ich dachte, es muss nicht wissen, welche Überladung verwendet werden soll, also habe ich dies (siehe unten) getan, um es explizit zu machen, aber ich bekomme immer noch das gleiche Problem!

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

Ich weiß, dass ich das umgehen kann, indem ich nach Null suche oder nicht einmal Null übergebe, aber das ist nicht der Punkt. Warum passiert dies?

Andrew Bullock
quelle

Antworten:

349

Andrew Ich denke, das Problem, das Sie bekommen, ist ein Ergebnis der RenderPartial-Methode, die das Modell des Aufrufs (view) für die Teilansicht verwendet, wenn das Modell, das Sie übergeben, null ist. Sie können dieses seltsame Verhalten umgehen, indem Sie Folgendes tun:

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

Hilft das?

meandmycode
quelle
16
Immer noch Zeit sparen. Ich zog mir darüber die Haare aus.
James Gregory
3
Ich verstehe, warum sie das Nullmodell unterstützen und das Seitenmodell übergeben, aber sie hätten das nicht durch Überladen bewältigen können. @ Html.Render ("Esel") ist anders als @ Html.Render ("Esel", könnte nicht)
Phil Strong
19
Ich finde das sehr eingängig, also habe ich ein "Problem" hinzugefügt. Stimmen Sie darüber ab, wenn Sie damit einverstanden sind: aspnet.codeplex.com/workitem/8872
pbz
3
Ich habe festgestellt, dass mit dieser Lösung meine ValidationSummary in meiner Teilansicht nicht funktioniert hat, da die ViewData des primären Modells in der Teilansicht verloren gegangen sind. Ich habe die hier gegebene Antwort stackoverflow.com/a/12037580/649497 verwendet , um dies zu lösen.
BruceHill
5
Sie sollten die vorhandenen ViewData weitergeben: neues ViewDataDictionary (ViewData)
ScottE
48

Die Antwort von @ myandmycode ist gut, aber eine etwas kürzere wäre

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

Dies funktioniert, weil das ViewDataDictionaryModell das Modell enthält und ein Modell als Konstruktorparameter akzeptiert werden kann. Dies übergibt grundsätzlich ein "gesamtes" Ansichtsdatenwörterbuch, das natürlich nur das möglicherweise Null-Modell enthält.

Konfigurator
quelle
2
@jcmcbeth: Ähm, nein, das tut es nicht ... Ich habe genau diesen Code mit Nullen erfolgreich verwendet.
Konfigurator
1
@jcmcbeth: Benutzt du new ViewDataDictionary(null)? Denn das würde eine andere Überladung auswählen, eine mit einem ViewDataDictionaryParameter, der wahrscheinlich keine Nullen akzeptieren würde.
Konfigurator
1
Es scheint, dass die Verwendung einer ViewBag-Eigenschaft dazu führt, dass der falsche Konstruktor aufgerufen wird. Wie es einen dynamischen Typ annimmt und davon ausgeht, dass es sich um ein ViewDataDictionary über einem Objekt handelt, macht für mich keinen Sinn, aber es scheint das zu sein, was es tut. Sie müssen es in ein Objekt umwandeln, damit es den richtigen Konstruktor auswählt.
Joel McBeth
1
@jcmcbeth: Wenn Sie es über einen dynamischen Typ aufrufen, wird dasselbe verwendet, als hätten Sie den tatsächlichen Wert angegeben. Wenn der Wert lautet null, entspricht dies dem Aufruf new ViewDataDictionary(null), wodurch die spezifischste Überladung aufgerufen wird.
Konfigurator
1
Wenn Sie es so verwenden, ist der Wörterbuchfehler verschwunden. Html.RenderPartial("TaskList", new ViewDataDictionary(model: Model.Tasks))Sie verwenden den falschen Konstruktor, wenn er null ist.
Filip Cornelissen
26

Es scheint, dass MVC absichtlich zum "übergeordneten" Modell zurückkehrt, wenn die Eigenschaft des Modells, das Sie übergeben, null ist. Anscheinend interpretiert die MVC-Engine einen Nullmodellwert als Absicht, den vorherigen zu verwenden.

Etwas mehr Details hier: ASP.NET MVC, stark typisierte Ansichten, Fehler bei Teilansichtsparametern

Zack
quelle
1
+1 für den tatsächlichen Versuch, das Problem zu erklären und dies nicht nur als seltsames Verhalten zu behandeln
YavgenyP
Ja, das ist mir passiert und das oben Genannte hat es nicht behoben, es hat mir nur ein bisschen mehr Informationen über meinen tatsächlichen Fehler gegeben.
Leinwand
20

Wenn Sie Ihre vorherigen ViewData nicht in der Teilansicht verlieren möchten, können Sie Folgendes versuchen:

<% Html.RenderPartial("TaskList", Model.Tasks, new ViewDataDictionary(ViewData){Model = null});%>
Fran P.
quelle
1
Dies scheint die Frage nicht zu beantworten.
John Saunders
6
+1 Eigentlich funktioniert es. Es ist im Grunde die gleiche Idee, die hier vorgestellt wird. Stackoverflow.com/a/713921/649497 überwindet jedoch ein Problem mit dieser Antwort, und das heißt, dass die ViewData verloren gehen, wenn Sie das ViewDataDictionary mit einem leeren Konstruktor instanziieren. Ich habe dieses Problem zuerst mit der akzeptierten Lösung gelöst und dann festgestellt, dass meine ValidationSummary in der Teilansicht nicht funktioniert hat. Diese Lösung hat das für mich gelöst. Diese Antwort erfordert mehr Anerkennung, um das Problem zu lösen und ViewData in Ihrer Teilansicht beizubehalten.
BruceHill
1
@Franc P Dies funktionierte tatsächlich, ohne die ViewBag-Werte zu verlieren, und übergab daher ein Nullmodell. Vielen Dank.
Zaker
Dies ist die richtige Antwort, wenn Sie ViewBag-Zugriff in Ihren Partials benötigen!
Daniel Lorenz
12

Eine Lösung wäre, einen HtmlHelper wie folgt zu erstellen:

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);
}

Das Partial<T>(...)stimmte vor dem Partial(...)so bequemen und kein Mehrdeutigkeitsfehler beim Kompilieren überein .

Persönlich fällt es mir schwer, das Verhalten zu verstehen - scheint es schwer vorstellbar, dass dies eine Designentscheidung ist?

Colin Breame
quelle
1
Das habe ich am Ende getan. Es gibt nicht viele Designoptionen / Verhaltensweisen in asp.net mvc, die irgendeinen Sinn ergeben. da hat es aufgegeben. hilfreich für andere tho, so haben eine +1
Andrew Bullock
Gut, jedoch für den Benutzer unklar. Nehmen wir an, ich bin an das gewöhnt, was mein Kollege in seinem Projekt verwendet. Ich beginne ein neues. Dann vergessen Sie völlig, diese Überladung und Voilla hinzuzufügen. Die Ausnahmen treten in der Produktion auf, weil wir sie nicht gut genug getestet haben. Ein anderer Name ist besser imho.
Jaap
11

Obwohl dies beantwortet wurde, bin ich darauf gestoßen und habe beschlossen, dieses Problem für mein Projekt zu lösen, anstatt es zu umgehen new ViewDataDictionary().

Ich habe eine Reihe von Erweiterungsmethoden erstellt: https://github.com/q42jaap/PartialMagic.Mvc/blob/master/PartialMagic.Mvc/PartialExtensions.cs
Ich habe auch einige Methoden hinzugefügt, die den Teil nicht aufrufen, wenn das Modell null ist Dies spart eine Menge if-Anweisungen.

Ich habe sie für Razor erstellt, aber einige von ihnen sollten auch mit Ansichten im Aspx-Stil funktionieren (diejenigen, die HelperResult verwenden, sind wahrscheinlich nicht kompatibel).

Die Erweiterungsmethoden sehen folgendermaßen aus:

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

Es gibt auch Methoden für IEnumerable<object>Modelle, und die verworfenen können auch mit einem Razor-Lambda aufgerufen werden, mit dem Sie das Teilergebnis mit etwas HTML umschließen können.

Fühlen Sie sich frei, sie zu verwenden, wenn Sie möchten.

Jaap
quelle
1
Immer noch nützlich ab MVC5: 25.06.2014. Vielen Dank.
Jason
1

Meine Problemumgehung hierfür ist:


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

h3n
quelle
Dies ist eine schmutzige Lösung. In Ihrer Teilansicht sollten Sie in der Lage sein, nach Nullmodellen zu suchen, anstatt zu prüfen, ob die Liste Werte enthält und ob sie Null ist.
madd