ASP.NET MVC Razor passa il modello al layout


97

Quello che vedo è una proprietà Layout di stringa. Ma come posso passare esplicitamente un modello al layout?


Ho diverse pagine con un modello diverso ma lo stesso layout
SiberianGuy

2
Questa domanda su stackoverflow sembra rispondere a ciò che stai chiedendo: stackoverflow.com/questions/13225315/…
Paul,

Non ho questo problema. Modelè disponibile in _Layout. Sto usando MVC5.
Toddmo

Risposte:


66

Sembra che tu abbia modellato i tuoi viewmodels un po 'in modo sbagliato se hai questo problema.

Personalmente non scriverei mai una pagina di layout. Ma se vuoi farlo, dovresti avere un viewmodel di base da cui ereditano gli altri tuoi viewmodel e digita il tuo layout nel viewmodel di base e le pagine nello specifico una volta.


11
"Personalmente non scriverei mai una pagina di layout." Perché? Voglio dire, come gestisci il contenuto dinamico laterale che appare in Tutte le pagine? Salti i controller dalla vista? / forse intendi usare RenderAction dal layout? (Lo sto solo guardando in questo momento)
eglasius

52
@eglasius, La soluzione che utilizzo è diversa a seconda del tipo di contenuto di cui parliamo. Ma una soluzione comune consiste nell'usare RenderAction per eseguire il rendering delle parti che richiedono i propri dati nella pagina di layout. Il motivo per cui non mi piace digitare la pagina di layout è che ti costringerà ad ereditare sempre un viewmodel "base" in tutti i tuoi specifici modelli di visualizzazione. Nella mia esperienza di solito questa non è un'idea molto buona e molte volte avrai problemi quando è troppo tardi per cambiare il design (o ci vorrà troppo tempo).
Mattias Jakobsson

2
E se volessi includere il modello di base per aggregazione, non per eredità? Un modo perfettamente legittimo dal punto di vista del design. Allora come gestisco il layout?
Fyodor Soikin

4
Ho 2 soluzioni: un modello generico per il layout in modo da poter utilizzare MyLayoutModel <MyViewModel> per il modello di visualizzazione, utilizzando RenderPartial con MyViewModel solo nel layout. Oppure eseguire il rendering parziale delle parti della pagina utilizzando RenderAction per le parti memorizzate nella cache statica e chiamate ajax per le parti dinamiche. Ma preferisco la prima soluzione in quanto è più adatta ai motori di ricerca e può essere facilmente combinata con gli aggiornamenti ajax.
Softlion

4
Lavorando su codice legacy dove esattamente questo è stato fatto. È un incubo. Non digitare i layout ... per favore!

79
  1. Aggiungi una proprietà al tuo controller (o controller di base) chiamata MainLayoutViewModel (o qualsiasi altra cosa) con il tipo che desideri utilizzare.
  2. Nel costruttore del controller (o controller di base), creare un'istanza del tipo e impostarlo sulla proprietà.
  3. Impostalo sul campo ViewData (o ViewBag)
  4. Nella pagina Layout, trasmetti quella proprietà al tuo tipo.

Esempio: Controller:

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

}

Esempio di inizio pagina di layout

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

Ora puoi fare riferimento alla variabile "viewModel" nella pagina di layout con accesso completo all'oggetto digitato.

Mi piace questo approccio perché è il controller che controlla il layout, mentre i singoli modelli di visualizzazione della pagina rimangono indipendenti dal layout.

Note per MVC Core


Mvc Core sembra spazzare via il contenuto di ViewData / ViewBag dopo aver chiamato ogni azione per la prima volta. Ciò significa che l'assegnazione di ViewData nel costruttore non funziona. Ciò che funziona, tuttavia, è usare un IActionFiltere fare lo stesso identico lavoro in OnActionExecuting. Metti MyActionFilteril tuo 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;
            }
        }
    }

1
Capisco ... ma le dinamiche / i cast sono piuttosto centrali nelle pagine del rasoio. Una cosa che potresti fare è aggiungere un metodo statico a MainLayoutViewModel che esegue il casting per te (ad esempio MainLayoutViewModel.FromViewBag (this.ViewBag)) in modo che almeno il cast avvenga in un posto e puoi gestire meglio le eccezioni lì.
BlackjacketMack

@BlackjacketMack Buon approccio e l'ho raggiunto usando quanto sopra e apportando alcune modifiche bcoz avevo un requisito di diff e questo mi ha davvero aiutato, grazie. Possiamo ottenere lo stesso utilizzando TempData se sì, allora come e no, allora dimmi perché non può essere utilizzato. Grazie ancora.
Zaker

2
@User - TempData usa Session e mi sembra sempre un po 'complicato. La mia comprensione è che è "read-once" in modo che non appena lo leggi lo rimuove dalla sessione (o forse non appena la richiesta è terminata). È possibile che memorizzi la sessione in Sql Server (o Dynamo Db), quindi considera il fatto che dovresti serializzare MasterLayoutViewModel ... non quello che vuoi molto probabilmente. Quindi, in pratica, impostandolo su ViewData lo memorizza in memoria in un dizionario poco flessibile, che si adatta al conto.
BlackjacketMack

Abbastanza semplice, ho usato la tua soluzione ma sono nuovo in MVC quindi, mi chiedo solo che sia considerata una buona pratica? o almeno non male?
Karim AG

1
Ciao Karim AG, penso che sia un po 'entrambe le cose. Tendo a considerare l'archiviazione di cose in ViewData come una cattiva pratica (è difficile da tracciare, basata sul dizionario, non proprio digitata) ... MA ... digitare tutte le proprietà del layout in un oggetto fortemente tipizzato è un'ottima pratica. Quindi faccio un compromesso dicendo, ok, memorizziamo una cosa lì, ma blocca il resto su un buon ViewModel fortemente tipizzato.
BlackjacketMack

30

questa è roba piuttosto semplice, tutto ciò che devi fare è creare un modello di visualizzazione di base e assicurarti che TUTTO! e intendo TUTTI! delle tue visualizzazioni che utilizzeranno mai quel layout riceveranno visualizzazioni che utilizzano quel modello di base!

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

public class EmptyViewModel : ViewModelBase
{
}

public abstract class ViewModelBase
{
}

in _Layout.cshtml:

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

nel metodo Index (ad esempio) nel controller home:

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

il Index.cshtml:

@model Models.SomeViewModel

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

<div class="row">

Non sono d'accordo sul fatto che il passaggio di un modello a _layout sia un errore, alcune informazioni utente possono essere passate ei dati possono essere popolati nella catena di ereditarietà del controller, quindi è necessaria solo un'implementazione.

ovviamente per scopi più avanzati dovresti prendere in considerazione la creazione di un contenuto statico personalizzato utilizzando l'iniezione e includere lo spazio dei nomi del modello in _Layout.cshtml.

ma per gli utenti di base questo farà il trucco


Sono d'accordo con te. Grazie.
Sebastián Guerrero

1
e voglio solo menzionare che funziona anche con un'interfaccia invece della classe base
VladL

27

Una soluzione comune è quella di creare un modello di visualizzazione di base che contenga le proprietà utilizzate nel file di layout e quindi ereditare dal modello di base ai modelli utilizzati nelle rispettive pagine.

Il problema con questo approccio è che ora ti sei bloccato nel problema di un modello che può ereditare solo da un'altra classe, e forse la tua soluzione è tale che non puoi comunque usare l'ereditarietà sul modello che intendi.

La mia soluzione inizia anche con un modello di visualizzazione di base:

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

    public string Title { get;}
}

Quello che uso quindi è una versione generica del LayoutModel che eredita dal LayoutModel, in questo modo:

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

    public T PageModel { get; }
}

Con questa soluzione ho scollegato la necessità di avere l'ereditarietà tra il modello di layout e il modello.

Quindi ora posso andare avanti e utilizzare LayoutModel in Layout.cshtml in questo modo:

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

E su una pagina puoi usare il LayoutModel generico in questo modo:

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

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

Dal tuo controller restituisci semplicemente un modello di tipo LayoutModel:

public ActionResult Page()
{
    return View(new LayoutModel<Customer>(new Customer() { Name = "Test" }, "Title");
}

1
Bonus per aver sottolineato il problema dell'ereditarietà multipla e come affrontarlo! Questa è una risposta migliore per la scalabilità.
Brett Spencer

1
La migliore soluzione secondo me. Da un punto di vista architettonico è scalabile e manutenibile. Questo è il modo corretto per farlo. Non mi sono mai piaciuti ViewBag o ViewData ..... Mi sembrano entrambi hacky.
Jonathan Alfaro

10

Perché non aggiungi semplicemente una nuova vista parziale con il controller specifico di i che passa il modello richiesto alla vista parziale e infine il rendering della vista parziale menzionata sul tuo Layout.cshtml usando RenderPartial o RenderAction?

Uso questo metodo per mostrare le informazioni dell'utente connesso come nome, immagine del profilo e così via.


2
Puoi approfondire questo per favore? Apprezzerei un collegamento a qualche post sul blog che utilizza questa tecnica
J86

Può funzionare, ma perché subire il colpo di prestazioni? Bisogna attendere tutte le elaborazioni effettuate dal titolare, restituire la visualizzazione, solo per fare in modo che il browser dell'utente effettui UN'ALTRA richiesta per ottenere i dati necessari. E se il tuo layout dipende dai dati per il rendering appropriato. IMHO questa non è una risposta a questa domanda.
Brett Spencer

3

vecchia domanda ma solo per citare la soluzione per gli sviluppatori MVC5, è possibile utilizzare la Modelproprietà come in vista.

La Modelproprietà sia nella vista che nel layout è associata allo stesso ViewDataDictionaryoggetto, quindi non devi fare alcun lavoro extra per passare il tuo modello alla pagina del layout e non devi dichiararlo @model MyModelNamenel layout.

Notare però che quando si utilizza @Model.XXXnel layout il menu contestuale di intelliSense non verrà visualizzato perché Modelqui è un oggetto dinamico proprio come ViewBag.


2

Forse non è tecnicamente il modo corretto per gestirlo, ma la soluzione più semplice e ragionevole per me è semplicemente creare una classe e istanziarla nel layout. È un'eccezione una tantum al modo altrimenti corretto di farlo. Se questo viene fatto più che nel layout, è necessario ripensare seriamente a ciò che si sta facendo e magari leggere qualche altro tutorial prima di procedere ulteriormente nel progetto.

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

poi nella vista

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

in .net core puoi persino ignorarlo e utilizzare l'inserimento delle dipendenze.

@inject My.Namespace.IMyLayoutModel myLayoutModel

È una di quelle aree che è un po 'ombreggiata. Ma date le alternative estremamente complicate che vedo qui, penso che sia più che un'eccezione accettabile da fare in nome della praticità. Soprattutto se ti assicuri di mantenerlo semplice e ti assicuri che qualsiasi logica pesante (direi che in realtà non dovrebbe essercene, ma i requisiti differiscono) si trova in un'altra classe / livello a cui appartiene. È sicuramente meglio che inquinare TUTTI i tuoi controller o modelli per il gusto di fondamentalmente una sola vista.


2

C'è un altro modo per archiviarlo.

  1. Basta implementare la classe BaseController per tutti i controller .

  2. Nella BaseControllerclasse creare un metodo che restituisca una classe Model come per esempio.

public MenuPageModel GetTopMenu() 
{    

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

}
  1. E nella Layoutpagina puoi chiamare quel metodoGetTopMenu()
@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>

0

Supponiamo che il tuo modello sia una raccolta di oggetti (o forse un singolo oggetto). Per ogni oggetto nel modello eseguire le seguenti operazioni.

1) Metti l'oggetto che vuoi mostrare nel ViewBag. Per esempio:

  ViewBag.YourObject = yourObject;

2) Aggiungi un'istruzione using all'inizio di _Layout.cshtml che contiene la definizione della classe per i tuoi oggetti. Per esempio:

@using YourApplication.YourClasses;

3) Quando fai riferimento al tuoOggetto in _Layout, lancialo. Puoi applicare il cast a causa di ciò che hai fatto in (2).


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

Usa IContainsMyModel nel tuo layout.

Risolto. Regola delle interfacce.


1
non sono sicuro del motivo per cui sei stato votato. Utilizzando un'interfaccia, simile a ciò che hai fatto qui, ha funzionato nel mio contesto.
costa

-6

Per esempio

@model IList<Model.User>

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

Ulteriori informazioni sulla nuova direttiva @model


Ma cosa succede se voglio passare il primo elemento della raccolta al modello Layout?
SiberianGuy

Devi recuperare il primo elemento nel controller e impostare il modello su @model Model.User
Martin Fabik

Ma voglio che la mia pagina riceva IList e Layout - solo il primo elemento
SiberianGuy

Se ho capito bene, vuoi che il modello sia un IList <SomeThing> e nella visualizzazione ottieni il primo elemento della collezione? Se è così, usa @ Model.First ()
Martin Fabik

6
Il poster chiedeva come passare un modello alla pagina _Layout.cshtml .. non alla vista principale che utilizza il layout.
Pure.Krome
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.