renderpartial avec un modèle nul obtient le mauvais type

198

J'ai une page:

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

Et là-dessus, ce qui suit:

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

Voici l'objet DTO:

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

et voici le partiel:

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

Lorsque Model.Tasks n'est pas nul, tout fonctionne bien. Cependant, lorsque sa valeur nulle, j'obtiens:

L'élément de modèle transmis au dictionnaire est de type «DTOSearchResults», mais ce dictionnaire nécessite un élément de modèle de type «System.Collections.Generic.IEnumerable» 1 [tâche] ».

J'ai pensé qu'il ne devait pas savoir quelle surcharge utiliser, alors j'ai fait cela (voir ci-dessous) pour être explicite, mais j'ai toujours le même problème!

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

Je sais que je peux contourner cela en vérifiant null, ou même en ne passant pas null, mais ce n'est pas le point. Pourquoi cela arrive-t-il?

Andrew Bullock
la source

Réponses:

349

Andrew Je pense que le problème que vous rencontrez est le résultat de la méthode RenderPartial utilisant le modèle de l'appel (vue) dans la vue partielle lorsque le modèle que vous passez est nul .. vous pouvez contourner ce comportement étrange en faisant:

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

Est ce que ça aide?

meandmycode
la source
16
Toujours gagner du temps aux gens. Je tirais mes cheveux dessus.
James Gregory
3
Je comprends pourquoi ils prennent en charge le modèle nul et transmettent le modèle de pages, mais ils n'ont pas pu gérer cela en surchargeant. @ Html.Render ("ânes") est différent de @ Html.Render ("ânes", pourrait-être)
Phil Strong
19
Je trouve cela très contre-intuitif donc j'ai ajouté un "problème", votez dessus si vous êtes d'accord: aspnet.codeplex.com/workitem/8872
pbz
3
J'ai trouvé qu'avec cette solution, mon ValidationSummary dans ma vue partielle ne fonctionnait pas car les ViewData du modèle principal étaient perdues dans la vue partielle. J'ai utilisé la réponse donnée ici stackoverflow.com/a/12037580/649497 pour résoudre ce problème.
BruceHill
5
Vous devez transmettre le ViewData existant: nouveau ViewDataDictionary (ViewData)
ScottE
48

La réponse de @ myandmycode est bonne, mais une réponse légèrement plus courte serait

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

Cela fonctionne parce que ViewDataDictionaryc'est l'élément qui contient le modèle, et il peut accepter un modèle comme paramètre constructeur. Cela passe essentiellement un dictionnaire de données de vue «entier», qui bien sûr ne contient que le modèle éventuellement nul.

configurateur
la source
2
@jcmcbeth: Euh, non, ce n'est pas le cas ... J'ai utilisé ce code exact avec des valeurs nulles avec succès.
configurateur
1
@jcmcbeth: Utilisez-vous new ViewDataDictionary(null)? Parce que cela choisirait une surcharge différente, une avec un ViewDataDictionaryparamètre, qui n'accepterait probablement pas les null.
configurateur
1
Il semblerait que l'utilisation d'une propriété ViewBag provoque l'appel du mauvais constructeur. Comment cela prend un type dynamique et suppose qu'il s'agit d'un ViewDataDictionary sur un objet n'a pas de sens pour moi, mais cela semble être ce qu'il fait. Vous devrez le convertir en un objet pour qu'il sélectionne le bon constructeur.
Joel McBeth
1
@jcmcbeth: L'appeler via un type dynamique utilise la même chose que si vous avez donné la valeur réelle; si la valeur est null, c'est la même chose que l'appel new ViewDataDictionary(null)qui provoque l' appel de la surcharge la plus spécifique.
configurateur
1
si vous l'utilisez comme ceci, l'erreur de dictionnaire a disparu. Html.RenderPartial("TaskList", new ViewDataDictionary(model: Model.Tasks))Vous utilisez le mauvais constructeur s'il est nul.
Filip Cornelissen
26

Il semble que lorsque la propriété du modèle que vous transmettez est nulle, MVC revient intentionnellement au modèle "parent". Apparemment, le moteur MVC interprète une valeur de modèle nulle comme l'intention d'utiliser la précédente.

Un peu plus de détails ici: ASP.NET MVC, vues fortement typées, problème de paramètres de vue partielle

Zack
la source
1
+1 pour avoir réellement tenté d'expliquer le problème, et pas seulement traité cela comme un comportement étrange
YavgenyP
Oui, cela m'arrivait et ce qui précède ne l'a pas corrigé, cela m'a juste donné un peu plus d'informations sur mon erreur réelle.
Canvas
20

Si vous ne souhaitez pas perdre vos ViewData précédentes dans la vue partielle, vous pouvez essayer:

<% Html.RenderPartial("TaskList", Model.Tasks, new ViewDataDictionary(ViewData){Model = null});%>
Fran P
la source
1
Cela ne semble pas répondre à la question.
John Saunders
6
+1 En fait, cela fonctionne. C'est fondamentalement la même idée présentée ici stackoverflow.com/a/713921/649497 mais surmonte un problème avec cette réponse et c'est que le ViewData disparaîtra si vous instanciez le ViewDataDictionary avec un constructeur vide. J'ai d'abord résolu ce problème avec la solution acceptée, puis j'ai constaté que mon résumé de validation ne fonctionnait pas dans la vue partielle. Cette solution a résolu cela pour moi. Cette réponse a besoin de plus de reconnaissance pour résoudre le problème et préserver ViewData dans votre vue partielle.
BruceHill
1
@Franc P cela a réellement fonctionné sans perdre les valeurs ViewBag et a donc passé un modèle nul. Merci.
Zaker
C'est la bonne réponse si vous avez besoin d'un accès ViewBag dans vos partiels!
Daniel Lorenz
12

Une solution serait de créer un HtmlHelper comme ceci:

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

L' Partial<T>(...)apparié avant l' Partial(...)erreur si pratique et sans ambiguïté lors de la compilation.

Personnellement, je trouve difficile de comprendre le comportement - semble difficile d'imaginer cela comme un choix de conception?

Colin Breame
la source
1
c'est ce que j'ai fait au final. il n'y a pas beaucoup de choix / comportements de conception dans asp.net mvc qui ont du sens. depuis l'abandonné. utile aux autres, alors ayez un +1
Andrew Bullock
Bon, mais peu clair pour l'utilisateur. Disons que je suis habitué à ce que mon collègue utilise dans son projet, j'en commence un nouveau. Ensuite, oubliez totalement d'ajouter cette surcharge et cette voix, les exceptions commencent à se produire en production parce que nous ne l'avons pas assez bien testé. Un autre nom est beter imho.
Jaap
11

Bien que cela ait été répondu, je suis tombé sur cela et j'ai décidé que je voulais résoudre ce problème pour mon projet au lieu de le contourner new ViewDataDictionary().

J'ai créé un ensemble de méthodes d'extension: https://github.com/q42jaap/PartialMagic.Mvc/blob/master/PartialMagic.Mvc/PartialExtensions.cs
J'ai également ajouté quelques méthodes qui n'appellent pas le partiel si le modèle est nul , cela économisera beaucoup d'instructions if.

Je les ai créés pour Razor, mais certains d'entre eux devraient également fonctionner avec des vues de style aspx (celles qui utilisent HelperResult ne sont probablement pas compatibles).

Les méthodes d'extension ressemblent à ceci:

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

Il existe également des méthodes pour IEnumerable<object> modèles et celles qui sont supprimées peuvent également être appelées avec un lambda Razor qui vous permet d'envelopper le résultat partiel avec du html.

N'hésitez pas à les utiliser si vous le souhaitez.

Jaap
la source
1
Toujours utile depuis MVC5: 25/06/2014. Merci.
Jason
1

Ma solution à cela est la suivante:


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

h3n
la source
C'est une sale solution. Sur votre vue partielle, vous devriez pouvoir vérifier le modèle nul, plutôt que de vérifier si la liste a des valeurs et si elle est nulle.
madd