renderpartial com modelo nulo recebe o tipo errado

198

Eu tenho uma página:

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

E nele, o seguinte:

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

Aqui está o objeto DTO:

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

e aqui está o parcial:

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

Quando Model.Tasks não é nulo, tudo funciona bem. No entanto, quando é nulo, recebo:

O item de modelo passado no dicionário é do tipo 'DTOSearchResults', mas esse dicionário requer um item de modelo do tipo 'System.Collections.Generic.IEnumerable`1 [Task]'.

Achei que ele não deveria saber qual sobrecarga usar, então fiz isso (veja abaixo) para ser explícito, mas continuo com o mesmo problema!

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

Eu sei que posso contornar isso, verificando se é nulo ou nem mesmo passando nulo, mas esse não é o ponto. Por que isso está acontecendo?

Andrew Bullock
fonte

Respostas:

349

Andrew: Acho que o problema que você está obtendo é resultado do método RenderPartial usando o modelo da chamada (view) para a vista parcial quando o modelo que você passa é nulo. Você pode contornar esse comportamento estranho fazendo o seguinte:

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

Isso ajuda?

meandmycode
fonte
16
Ainda economizando tempo para as pessoas. Eu estava puxando meu cabelo por causa disso.
James Gregory
3
Eu entendo por que eles suportam o modelo nulo e passam as páginas do modelo, mas eles não poderiam ter lidado com isso sobrecarregando. @ Html.Render ( "burros") é diferente do @ Html.Render ( "burros", couldbenull)
Phil Forte
19
Acho isso muito intuitivo então eu adicionei uma "questão", votar nele se você concorda: aspnet.codeplex.com/workitem/8872
PBZ
3
Descobri que, com esta solução, meu ValidationSummary na minha vista parcial não funcionava porque os ViewData do modelo primário foram perdidos na vista parcial. Eu usei a resposta dada aqui stackoverflow.com/a/12037580/649497 para resolver isso.
BruceHill
5
Você deve repassar o ViewData existente: new ViewDataDictionary (ViewData)
ScottE
48

A resposta de @ myandmycode é boa, mas uma resposta um pouco mais curta seria

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

Isso funciona porque ViewDataDictionaryé o que mantém o modelo e pode aceitar um modelo como parâmetro construtor. Isso basicamente passa um dicionário de dados de exibição "inteiro", que obviamente contém apenas o modelo possivelmente nulo.

configurador
fonte
2
@jcmcbeth: Erm, não, não ... Eu usei esse código exato com valores nulos com sucesso.
Configurator
1
@jcmcbeth: Você está usando new ViewDataDictionary(null)? Porque isso selecionaria uma sobrecarga diferente, uma com um ViewDataDictionaryparâmetro, que provavelmente não aceitaria nulos.
Configurator
1
Parece que o uso de uma propriedade ViewBag faz com que o construtor errado seja chamado. Como ele pega um tipo dinâmico e assume que é um ViewDataDictionary sobre um objeto não faz sentido para mim, mas parece ser o que está fazendo. Você precisará convertê-lo em um objeto para selecionar o construtor correto.
Joel McBeth
1
@jcmcbeth: chamá-lo por um tipo dinâmico usa o mesmo que se você tivesse fornecido o valor real; se o valor for null, é o mesmo que chamar, o new ViewDataDictionary(null)que causa a sobrecarga mais específica a ser chamada.
Configurator
1
se você usá-lo assim, o erro de dictionairy desapareceu. Html.RenderPartial("TaskList", new ViewDataDictionary(model: Model.Tasks))Você está usando o construtor errado, se for nulo.
Filip Cornelissen
26

Parece que quando a propriedade do Modelo que você está passando é nula, o MVC volta intencionalmente de volta ao Modelo "pai". Aparentemente, o mecanismo MVC interpreta um valor de modelo nulo como intenção de usar o anterior.

Um pouco mais de detalhes aqui: ASP.NET MVC, visualizações fortemente tipadas, falha parcial nos parâmetros da visualização

Zack
fonte
1
+1 por realmente tentar explicar o problema, e não apenas tratá-lo como um comportamento estranho
YavgenyP
Sim, isso estava acontecendo comigo e o exemplo acima não o corrigiu, apenas me deu um pouco mais de informação sobre o meu erro real.
Canvas
20

Se você não deseja perder seus ViewData anteriores na exibição parcial, tente:

<% Html.RenderPartial("TaskList", Model.Tasks, new ViewDataDictionary(ViewData){Model = null});%>
Fran P
fonte
1
Isso não parece responder à pergunta.
John Saunders
6
+1 Na verdade funciona. É basicamente a mesma idéia apresentada aqui stackoverflow.com/a/713921/649497, mas supera um problema com essa resposta e é que o ViewData desaparecerá se você instanciar o ViewDataDictionary com um construtor vazio. Resolvi esse problema com a solução aceita e descobri que meu ValidationSummary não funcionava na exibição parcial. Esta solução resolveu isso para mim. Esta resposta precisa de mais reconhecimento para resolver o problema e preservar ViewData em sua exibição parcial.
BruceHill
1
@Franc P isso realmente funcionou sem perder os valores do ViewBag e, portanto, passou um modelo nulo. Obrigado.
Zaker
Esta é a resposta certa se você precisar de acesso ao ViewBag nos seus Partials!
Daniel Lorenz
12

Uma solução seria criar um HtmlHelper assim:

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

A Partial<T>(...)correspondência antes do Partial(...)erro conveniente e sem ambiguidade ao compilar.

Pessoalmente, acho difícil entender o comportamento - parece difícil imaginar isso como uma escolha de design?

Colin Breame
fonte
1
Foi isso que fiz no final. não há muitas opções / comportamentos de design no asp.net mvc, o que faz algum sentido. desde que abandonou. útil para outras pessoas, então marque +1
Andrew Bullock
Bom, porém pouco claro para o usuário. Digamos que estou acostumado com o que meu colega usa em seu projeto, inicio um novo. Então esqueça totalmente de adicionar essa sobrecarga e voilla, as exceções começam a acontecer na produção porque não testamos bem o suficiente. Um nome diferente é beter imho.
21412 Jaap
11

Embora isso tenha sido respondido, deparei-me com isso e decidi que queria resolver esse problema do meu projeto, em vez de contorná-lo new ViewDataDictionary().

Criei um conjunto de métodos de extensão: https://github.com/q42jaap/PartialMagic.Mvc/blob/master/PartialMagic.Mvc/PartialExtensions.cs
Também adicionei alguns métodos que não chamam de parcial se o modelo for nulo , isso salvará muitas instruções if.

Eu os criei para o Razor, mas alguns deles também devem funcionar com visualizações no estilo aspx (as que usam HelperResult provavelmente não são compatíveis).

Os métodos de extensão são assim:

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

Também existem métodos para IEnumerable<object>modelos e os descartados também podem ser chamados com um lambda Razor que permite agrupar o resultado parcial com algum html.

Sinta-se livre para usá-los, se quiser.

Jaap
fonte
1
Ainda útil a partir do MVC5: 25/06/2014. Obrigado.
Jason
1

Minha solução alternativa para isso é:


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

h3n
fonte
Esta é uma solução suja. Na sua visão parcial, você deve poder verificar o Modelo nulo, em vez de verificar se a lista tem algum valor e se é nulo.
MADD