ASP.NET MVC Razor passa modelo para layout

97

O que vejo é uma propriedade de layout de string. Mas como posso passar um modelo para layout explicitamente?

SiberianGuy
fonte
Tenho várias páginas com modelo diferente, mas o mesmo layout
SiberianGuy
2
Esta questão stackoverflow parece responder ao que você está perguntando: stackoverflow.com/questions/13225315/…
Paul
Não estou tendo esse problema. Modelestá disponível em _Layout. Estou usando MVC5.
toddmo

Respostas:

66

Parece que você modelou seus viewmodels um pouco errados se tiver esse problema.

Pessoalmente, eu nunca digitaria uma página de layout. Mas se você quiser fazer isso, você deve ter um modelo de visão base do qual seus outros modelos de visão herdam e digitar seu layout para o modelo de visão base e suas páginas para o modelo específico uma vez.

Mattias Jakobsson
fonte
11
"Pessoalmente, eu nunca digitaria uma página de layout." Por quê? Quero dizer, como você lida com conteúdo dinâmico lateral que aparece em Todas as páginas? Você pula os controladores da visualização? / talvez você queira usar RenderAction no layout? (Estou apenas olhando para ele agora)
eglasius
52
@eglasius, A solução que uso é diferente dependendo do tipo de conteúdo sobre o qual falamos. Mas uma solução comum é usar RenderAction para renderizar partes que precisam de seus próprios dados na página de layout. O motivo pelo qual não gosto de digitar a página de layout é que isso o forçará a sempre herdar um modelo de visualização "base" em todos os modelos de visualização específicos. Na minha experiência, isso geralmente não é uma ideia muito boa e muitas vezes você terá problemas quando for tarde demais para alterar o design (ou demorará muito).
Mattias Jakobsson
2
E se eu quiser incluir o modelo básico por agregação, não por herança? Uma forma perfeitamente legítima do ponto de vista do design. Como faço para lidar com o layout então?
Fyodor Soikin
4
Eu tenho 2 soluções: um modelo genérico para o layout, então posso usar MyLayoutModel <MyViewModel> para o modelo de exibição, usando RenderPartial com MyViewModel apenas no layout. Ou renderize parcialmente as partes da página usando RenderAction para partes em cache estáticas e chamadas ajax para partes dinâmicas. Mas eu prefiro a primeira solução porque é mais amigável para os motores de busca e pode ser facilmente combinada com atualizações do Ajax.
Softlion
4
Trabalhar no código legado onde exatamente isso foi feito. É um pesadelo. Não digite seus layouts ... por favor!
79
  1. Adicione uma propriedade ao seu controlador (ou controlador básico) chamada MainLayoutViewModel (ou qualquer outro) com qualquer tipo que você gostaria de usar.
  2. No construtor do seu controlador (ou controlador básico), instancie o tipo e defina-o como a propriedade.
  3. Defina-o para o campo ViewData (ou ViewBag)
  4. Na página Layout, converta essa propriedade para o seu tipo.

Exemplo: Controlador:

public class MyController : Controller
{
    public MainLayoutViewModel MainLayoutViewModel { get; set; }

    public MyController()
    {
        this.MainLayoutViewModel = new MainLayoutViewModel();//has property PageTitle
        this.MainLayoutViewModel.PageTitle = "my title";

        this.ViewData["MainLayoutViewModel"] = this.MainLayoutViewModel;
    }

}

Exemplo de topo da página de layout

@{
var viewModel = (MainLayoutViewModel)ViewBag.MainLayoutViewModel;
}

Agora você pode referenciar a variável 'viewModel' em sua página de layout com acesso total ao objeto digitado.

Gosto dessa abordagem porque é o controlador que controla o layout, enquanto os modelos de visualização de página individuais permanecem agnósticos de layout.

Notas para MVC Core


Mvc Core parece explodir o conteúdo de ViewData / ViewBag ao chamar cada ação pela primeira vez. O que isso significa é que atribuir ViewData no construtor não funciona. O que funciona, no entanto, é usar um IActionFiltere fazer exatamente o mesmo trabalho em OnActionExecuting. Coloque MyActionFilterem seu MyController.

public class MyActionFilter: Attribute, IActionFilter
    {
        public void OnActionExecuted(ActionExecutedContext context)
        {
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            var myController= context.Controller as MyController;

            if (myController!= null)
            {
                myController.Layout = new MainLayoutViewModel
                {

                };

                myController.ViewBag.MainLayoutViewModel= myController.Layout;
            }
        }
    }
BlackjacketMack
fonte
1
Eu entendo ... mas a dinâmica / elencos são muito centrais para as páginas de navalha. Uma coisa que você pode fazer é adicionar um método estático ao MainLayoutViewModel que faz a conversão para você (por exemplo, MainLayoutViewModel.FromViewBag (this.ViewBag)) para que pelo menos a conversão ocorra em um lugar e você possa lidar melhor com as exceções lá.
BlackjacketMack
@BlackjacketMack Boa abordagem e consegui usando o acima e fazendo algumas modificações bcoz. Eu tinha um requisito de diff e isso realmente me ajudou, obrigado. Podemos conseguir o mesmo usando TempData se sim, então como e não, então por favor me diga por que ele não pode ser usado. Obrigado novamente.
Zaker
2
@User - TempData usa Session e sempre me parece um pouco desajeitado. Meu entendimento é que ele é 'lido uma vez', de modo que, assim que você lê-lo, ele o remove da sessão (ou talvez assim que a solicitação terminar). É possível que você armazene a sessão no Sql Server (ou Dynamo Db), então considere o fato de que você teria que serializar o MasterLayoutViewModel ... não o que você provavelmente deseja. Então, basicamente, configurá-lo como ViewData armazena-o na memória em um pequeno dicionário flexível, que se ajusta ao caso.
BlackjacketMack
Simples, usei sua solução, mas sou novo no MVC, então, estou pensando se isso é considerado uma boa prática? ou pelo menos não um mau?
Karim AG
1
Olá, Karim AG, acho que é um pouco dos dois. Eu tendo a considerar armazenar coisas em ViewData como uma prática ruim (é difícil rastrear, baseado em dicionário, não realmente digitado) ... MAS ... digitar todas as propriedades de layout em um objeto fortemente tipado é uma ótima prática. Então, eu me comprometo dizendo, ok, vamos armazenar uma coisa lá, mas ter o resto bloqueado em um ViewModel bem tipado.
BlackjacketMack
30

isso é muito básico, tudo o que você precisa fazer é criar um modelo de visualização básico e certificar-se de TUDO! e eu quero dizer TODOS! das suas visualizações que usarão aquele layout receberão visualizações que usam aquele modelo básico!

public class SomeViewModel : ViewModelBase
{
    public bool ImNotEmpty = true;
}

public class EmptyViewModel : ViewModelBase
{
}

public abstract class ViewModelBase
{
}

no _Layout.cshtml:

@model Models.ViewModelBase
<!DOCTYPE html>
  <html>
  and so on...

no método Index (por exemplo) no controlador doméstico:

    public ActionResult Index()
    {
        var model = new SomeViewModel()
        {
        };
        return View(model);
    }

o Index.cshtml:

@model Models.SomeViewModel

@{
  ViewBag.Title = "Title";
  Layout = "~/Views/Shared/_Layout.cshtml";
}

<div class="row">

Eu discordo que passar um modelo para _layout é um erro, algumas informações do usuário podem ser passadas e os dados podem ser preenchidos na cadeia de herança dos controladores, portanto, apenas uma implementação é necessária.

obviamente, para fins mais avançados, você deve considerar a criação de um contaxt estático customizado usando injeção e incluir esse namespace de modelo no _Layout.cshtml.

mas para usuários básicos isso fará o truque

Solar Yakir
fonte
Eu concordo com você. THX.
Sebastián Guerrero
1
e quero apenas mencionar que ele também funciona com uma interface em vez de classe base
VladL
27

Uma solução comum é fazer um modelo de vista base que contenha as propriedades usadas no arquivo de layout e então herdar do modelo base para os modelos usados ​​nas respectivas páginas.

O problema com essa abordagem é que agora você se prendeu ao problema de um modelo só pode herdar de uma outra classe, e talvez sua solução seja tal que você não possa usar herança no modelo que pretendia de qualquer maneira.

Minha solução também começa com um modelo de visualização de base:

public class LayoutModel
{
    public LayoutModel(string title)
    {
        Title = title;
    }

    public string Title { get;}
}

O que eu uso então é uma versão genérica do LayoutModel que herda do LayoutModel, assim:

public class LayoutModel<T> : LayoutModel
{
    public LayoutModel(T pageModel, string title) : base(title)
    {
        PageModel = pageModel;
    }

    public T PageModel { get; }
}

Com esta solução, desconectei a necessidade de haver herança entre o modelo de layout e o modelo.

Agora posso ir em frente e usar LayoutModel em Layout.cshtml assim:

@model LayoutModel
<!doctype html>
<html>
<head>
<title>@Model.Title</title>
</head>
<body>
@RenderBody()
</body>
</html>

E em uma página, você pode usar o LayoutModel genérico como este:

@model LayoutModel<Customer>
@{
    var customer = Model.PageModel;
}

<p>Customer name: @customer.Name</p>

Do seu controlador, você simplesmente retorna um modelo do tipo LayoutModel:

public ActionResult Page()
{
    return View(new LayoutModel<Customer>(new Customer() { Name = "Test" }, "Title");
}
Oskar Sjöberg
fonte
1
Bônus por apontar o problema de herança múltipla e como lidar com isso! Esta é uma resposta melhor para escalabilidade.
Brett Spencer
1
Melhor solução na minha opinião. Do ponto de vista arquitetônico, é escalonável e sustentável. Esta é a maneira correta de fazer isso. Eu nunca gostei de ViewBag ou ViewData ... Ambos parecem hacky para mim.
Jonathan Alfaro
10

Por que você simplesmente não adiciona uma nova vista parcial com o próprio controlador específico de i passando o modelo necessário para a vista parcial e, finalmente, renderiza a vista parcial mencionada em seu Layout.cshtml usando RenderPartial ou RenderAction?

Eu uso este método para mostrar as informações do usuário conectado, como nome, foto do perfil e etc.

Arya Sh
fonte
2
Você pode elaborar sobre isso, por favor? Agradeço um link para alguma postagem de blog que
aborda
Isso pode funcionar, mas por que sofrer o impacto no desempenho? Você tem que esperar por todo o processamento feito pelo controlador, retornar a visualização, apenas para que o navegador do usuário faça OUTRA solicitação para obter os dados necessários. E se o seu Layout depender dos dados para renderizar adequadamente. IMHO, esta não é uma resposta a esta pergunta.
Brett Spencer
3

velha questão, mas apenas para mencionar a solução para desenvolvedores MVC5, você pode usar a Modelmesma propriedade do modo de exibição.

A Modelpropriedade na visualização e no layout é associada ao mesmo ViewDataDictionaryobjeto, portanto, você não precisa fazer nenhum trabalho extra para passar seu modelo para a página de layout e não precisa declarar @model MyModelNameno layout.

Mas observe que quando você usa @Model.XXXno layout, o menu de contexto do intelliSense não aparecerá porque o Modelhere é um objeto dinâmico exatamente como ViewBag.

Laz Ziya
fonte
2

Talvez não seja tecnicamente a maneira correta de lidar com isso, mas a solução mais simples e razoável para mim é apenas fazer uma classe e instanciá-la no layout. É uma exceção única à maneira correta de fazê-lo. Se isso for feito mais do que no layout, você precisa repensar seriamente o que está fazendo e talvez ler mais alguns tutoriais antes de prosseguir em seu projeto.

public class MyLayoutModel {
    public User CurrentUser {
        get {
            .. get the current user ..
        }
    }
}

então na vista

@{
    // Or get if from your DI container
    var myLayoutModel = new MyLayoutModel();
}

no núcleo .net você pode até pular isso e usar injeção de dependência.

@inject My.Namespace.IMyLayoutModel myLayoutModel

É uma daquelas áreas meio sombrias. Mas, dadas as alternativas extremamente complicadas que estou vendo aqui, acho que é mais do que uma exceção normal a se fazer em nome da praticidade. Especialmente se você se certificar de mantê-lo simples e certificar-se de que qualquer lógica pesada (eu diria que realmente não deveria haver nenhuma, mas os requisitos são diferentes) está em outra classe / camada a que pertence. É certamente melhor do que poluir TODOS os seus controladores ou modelos por causa de basicamente apenas uma visualização.

computrius
fonte
2

Existe outra maneira de arquivá-lo.

  1. Basta implementar a classe BaseController para todos os controladores .

  2. Na BaseControllerclasse crie um método que retorne uma classe Model como por exemplo.

public MenuPageModel GetTopMenu() 
{    

var m = new MenuPageModel();    
// populate your model here    
return m; 

}
  1. E na Layoutpágina você pode chamar esse métodoGetTopMenu()
@using GJob.Controllers

<header class="header-wrapper border-bottom border-secondary">
  <div class="sticky-header" id="appTopMenu">
    @{
       var menuPageModel = ((BaseController)this.ViewContext.Controller).GetTopMenu();
     }
     @Html.Partial("_TopMainMenu", menuPageModel)
  </div>
</header>
Desenvolvedor
fonte
0

Vamos supor que seu modelo seja uma coleção de objetos (ou talvez um único objeto). Para cada objeto no modelo, faça o seguinte.

1) Coloque o objeto que você deseja exibir no ViewBag. Por exemplo:

  ViewBag.YourObject = yourObject;

2) Adicione uma instrução using no topo de _Layout.cshtml que contém a definição da classe para seus objetos. Por exemplo:

@using YourApplication.YourClasses;

3) Quando você faz referência a seuObjeto em _Layout, faça a projeção. Você pode aplicar o elenco por causa do que você fez em (2).

HappyPawn8
fonte
-2
public interface IContainsMyModel
{
    ViewModel Model { get; }
}

public class ViewModel : IContainsMyModel
{
    public string MyProperty { set; get; }
    public ViewModel Model { get { return this; } }
}

public class Composition : IContainsMyModel
{
    public ViewModel ViewModel { get; set; }
}

Use IContainsMyModel em seu layout.

Resolvido. Regra de interfaces.

Vai
fonte
1
não tenho certeza por que você foi rejeitado. Usar uma interface, semelhante ao que você fez aqui, funcionou no meu contexto.
costa
-6

Por exemplo

@model IList<Model.User>

@{
    Layout="~/Views/Shared/SiteLayout.cshtml";
}

Leia mais sobre a nova diretiva @model

Martin Fabik
fonte
Mas e se eu quiser passar o primeiro elemento da coleção para o modelo de Layout?
SiberianGuy
Você deve buscar o primeiro elemento em seu controlador e definir o modelo como @model Model.User
Martin Fabik
Mas eu quero que minha página tenha IList e Layout - apenas o primeiro elemento
SiberianGuy
Se eu entendi bem, você quer que o modelo seja um IList <SomeThing> e na visualização obtenha o primeiro elemento da coleção? Se sim, use @ Model.First ()
Martin Fabik
6
O autor do pôster estava perguntando sobre como passar um modelo para a página _Layout.cshtml .. não para a visualização principal que usa o layout.
Pure.Krome de