ASP.NET MVC Razor passe le modèle à la disposition

97

Ce que je vois est une propriété de mise en page de chaîne. Mais comment puis-je passer un modèle à la mise en page explicitement?

SibérienGuy
la source
J'ai plusieurs pages avec un modèle différent mais la même mise en page
SiberianGuy
2
Cette question stackoverflow semble répondre à ce que vous demandez: stackoverflow.com/questions/13225315/…
Paul
Je n'ai pas ce problème. Modelest disponible en _Layout. J'utilise MVC5.
toddmo

Réponses:

66

Il semble que vous ayez un peu mal modélisé vos modèles de vue si vous rencontrez ce problème.

Personnellement, je ne taperais jamais une page de mise en page. Mais si vous voulez faire cela, vous devriez avoir un modèle de vue de base dont vos autres modèles de vue héritent et tapez votre mise en page dans le modèle de vue de base et vous pages vers le modèle spécifique une fois.

Mattias Jakobsson
la source
11
"Personnellement, je ne taperais jamais une page de mise en page." Pourquoi? Je veux dire, comment gérez-vous le contenu dynamique latéral qui apparaît dans Toutes les pages? Ignorez-vous les contrôleurs de la vue? / peut-être voulez-vous utiliser RenderAction à partir de la mise en page? (Je regarde juste maintenant)
eglasius
52
@eglasius, La solution que j'utilise est différente selon le type de contenu dont nous parlons. Mais une solution courante consiste à utiliser RenderAction pour rendre les parties qui ont besoin de leurs propres données dans la page de mise en page. La raison pour laquelle je n'aime pas taper la page de mise en page est que cela vous obligera à toujours hériter d'un modèle de vue "de base" dans tous vos modèles de vue spécifiques. D'après mon expérience, ce n'est généralement pas une très bonne idée et la plupart du temps, vous aurez des problèmes lorsqu'il est trop tard pour modifier le design (ou cela prendra trop de temps).
Mattias Jakobsson
2
Que faire si je souhaite inclure le modèle de base par agrégation et non par héritage? Une manière parfaitement légitime du point de vue du design. Comment gérer la mise en page alors?
Fyodor Soikin
4
J'ai 2 solutions: un modèle générique pour la mise en page afin que je puisse utiliser MyLayoutModel <MyViewModel> pour le modèle de vue, en utilisant RenderPartial avec MyViewModel uniquement dans la mise en page. Ou restituer partiellement les parties de la page en utilisant RenderAction pour les parties statiques en cache et les appels ajax pour les parties dynamiques. Mais je préfère la première solution car elle est plus conviviale pour les moteurs de recherche et se combine facilement avec les mises à jour ajax.
Softlion
4
Travailler sur du code hérité là où cela a été fait exactement. C'est un cauchemar. Ne tapez pas vos mises en page ... s'il vous plaît!
79
  1. Ajoutez une propriété à votre contrôleur (ou contrôleur de base) appelée MainLayoutViewModel (ou autre) avec le type que vous souhaitez utiliser.
  2. Dans le constructeur de votre contrôleur (ou contrôleur de base), instanciez le type et définissez-le sur la propriété.
  3. Définissez-le sur le champ ViewData (ou ViewBag)
  4. Dans la page Disposition, transtypez cette propriété en votre type.

Exemple: contrôleur:

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

}

Exemple de haut de page de mise en page

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

Vous pouvez maintenant référencer la variable 'viewModel' dans votre page de mise en page avec un accès complet à l'objet tapé.

J'aime cette approche parce que c'est le contrôleur qui contrôle la mise en page, tandis que les modèles de vue de page individuels restent indépendants de la mise en page.

Notes pour MVC Core


Mvc Core semble épater le contenu de ViewData / ViewBag lors de l'appel de chaque action la première fois. Cela signifie que l'affectation de ViewData dans le constructeur ne fonctionne pas. Ce qui fonctionne, cependant, c'est d'utiliser un IActionFilteret de faire exactement le même travail dans OnActionExecuting. Mettez MyActionFiltervotre 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
la source
1
Je comprends ... mais la dynamique / les moulages sont assez centraux pour les pages de rasoir. Une chose que vous pouvez faire est d'ajouter une méthode statique à MainLayoutViewModel qui effectue le casting pour vous (par exemple MainLayoutViewModel.FromViewBag (this.ViewBag)) afin qu'au moins le cast se déroule à un endroit et vous pouvez mieux gérer les exceptions là-bas.
BlackjacketMack
@BlackjacketMack Bonne approche et je l'ai réalisé en utilisant ce qui précède et en apportant quelques modifications bcoz j'avais une exigence de diff et cela m'a vraiment aidé merci. Pouvons-nous obtenir la même chose en utilisant TempData si oui, comment et non, alors veuillez me dire pourquoi il ne peut pas être utilisé. Merci encore.
Zaker
2
@User - TempData utilise Session et me semble toujours un peu dérangé. Je crois comprendre qu'il est «lecture seule» de sorte que dès que vous le lisez, il le supprime de la session (ou peut-être dès que la demande est terminée). Il est possible que vous stockiez la session dans Sql Server (ou Dynamo Db), alors considérez le fait que vous deviez sérialiser le MasterLayoutViewModel ... pas ce que vous voulez probablement. Donc, en gros, le définir sur ViewData le stocke en mémoire dans un petit dictionnaire flexible, ce qui correspond à la facture.
BlackjacketMack
Assez simple, j'ai utilisé votre solution mais, je suis nouveau dans MVC donc, je me demande simplement si cela est considéré comme une bonne pratique? ou du moins pas une mauvaise?
Karim AG
1
Salut Karim AG, je pense que c'est un peu des deux. J'ai tendance à considérer stocker des éléments dans ViewData comme une mauvaise pratique (c'est difficile à suivre, basé sur un dictionnaire, pas vraiment tapé) ... MAIS ... taper toutes vos propriétés de mise en page dans un objet fortement typé est une bonne pratique. Je fais donc un compromis en disant, ok, stockons une chose là-dedans, mais verrouillons le reste dans un bon ViewModel fortement typé.
BlackjacketMack
30

ce sont des choses assez basiques, tout ce que vous avez à faire est de créer un modèle de vue de base et de vous assurer que TOUT! et je veux dire TOUT! de vos vues qui utiliseront cette disposition recevront des vues qui utilisent ce modèle de base!

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

public class EmptyViewModel : ViewModelBase
{
}

public abstract class ViewModelBase
{
}

dans le _Layout.cshtml:

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

dans la méthode Index (par exemple) dans le contrôleur domestique:

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

l'index.cshtml:

@model Models.SomeViewModel

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

<div class="row">

Je ne suis pas d'accord pour dire que passer un modèle à _layout est une erreur, certaines informations utilisateur peuvent être transmises et les données peuvent être renseignées dans la chaîne d'héritage des contrôleurs, de sorte qu'une seule implémentation est nécessaire.

évidemment, pour des raisons plus avancées, vous devriez envisager de créer un contaxt statique personnalisé à l'aide de l'injection et d'inclure cet espace de noms de modèle dans le _Layout.cshtml.

mais pour les utilisateurs basiques, cela fera l'affaire

Manoir Yakir
la source
Je suis d'accord avec toi. THX.
Sebastián Guerrero
1
et je veux juste mentionner qu'il fonctionne également avec une interface au lieu de la classe de base
VladL
27

Une solution courante consiste à créer un modèle de vue de base qui contient les propriétés utilisées dans le fichier de mise en page, puis à hériter du modèle de base aux modèles utilisés sur les pages respectives.

Le problème avec cette approche est que vous vous êtes maintenant enfermé dans le problème d'un modèle qui ne peut hériter que d'une autre classe, et peut-être que votre solution est telle que vous ne pouvez pas utiliser l'héritage sur le modèle que vous vouliez de toute façon.

Ma solution commence également par un modèle de vue de base:

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

    public string Title { get;}
}

Ce que j'utilise ensuite est une version générique du LayoutModel qui hérite du LayoutModel, comme ceci:

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

    public T PageModel { get; }
}

Avec cette solution, j'ai déconnecté la nécessité d'avoir un héritage entre le modèle de disposition et le modèle.

Alors maintenant, je peux continuer et utiliser le LayoutModel dans Layout.cshtml comme ceci:

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

Et sur une page, vous pouvez utiliser le LayoutModel générique comme ceci:

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

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

Depuis votre contrôleur, vous renvoyez simplement un modèle de type LayoutModel:

public ActionResult Page()
{
    return View(new LayoutModel<Customer>(new Customer() { Name = "Test" }, "Title");
}
Oskar Sjöberg
la source
1
Bonus pour avoir signalé le problème de l'héritage multiple et comment y faire face! C'est une meilleure réponse pour l'évolutivité.
Brett Spencer
1
Meilleure solution à mon avis. D'un point de vue architectural, il est évolutif et maintenable. C'est la bonne façon de procéder. Je n'ai jamais aimé ViewBag ou ViewData ..... Ils me semblent tous les deux piratés.
Jonathan Alfaro
10

Pourquoi ne pas simplement ajouter une nouvelle vue partielle avec le contrôleur spécifique de i en passant le modèle requis à la vue partielle et enfin rendre la vue partielle mentionnée sur votre Layout.cshtml en utilisant RenderPartial ou RenderAction?

J'utilise cette méthode pour afficher les informations de l'utilisateur connecté telles que le nom, la photo de profil, etc.

Arya Sh
la source
2
Pouvez-vous élaborer là-dessus s'il vous plaît? J'apprécierais un lien vers un article de blog qui passe par cette technique
J86
Cela peut fonctionner, mais pourquoi prendre la performance? Vous devez attendre tout le traitement effectué par le responsable du traitement, renvoyer la vue, pour que le navigateur de l'utilisateur fasse une AUTRE demande pour obtenir les données nécessaires. Et si votre mise en page dépend des données pour un rendu approprié. IMHO ce n'est pas une réponse à cette question.
Brett Spencer
3

ancienne question mais juste pour mentionner la solution pour les développeurs MVC5, vous pouvez utiliser la Modelmême propriété que dans la vue.

La Modelpropriété dans la vue et la mise en page est associée au même ViewDataDictionaryobjet, vous n'avez donc pas à faire de travail supplémentaire pour transmettre votre modèle à la page de mise en page, et vous n'avez pas à déclarer @model MyModelNamedans la mise en page.

Mais notez que lorsque vous utilisez @Model.XXXdans la mise en page, le menu contextuel intelliSense n'apparaîtra pas car Modelici est un objet dynamique comme ViewBag.

Laz Ziya
la source
2

Ce n'est peut-être pas techniquement la bonne façon de le gérer, mais la solution la plus simple et la plus raisonnable pour moi est simplement de créer une classe et de l'instancier dans la mise en page. C'est une exception ponctuelle à la manière par ailleurs correcte de procéder. Si cela est fait plus que dans la mise en page, vous devez repenser sérieusement ce que vous faites et peut-être lire quelques tutoriels supplémentaires avant de progresser dans votre projet.

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

puis dans la vue

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

dans .net core, vous pouvez même ignorer cela et utiliser l'injection de dépendances.

@inject My.Namespace.IMyLayoutModel myLayoutModel

C'est l'un de ces domaines qui est un peu ombragé. Mais étant donné les alternatives extrêmement compliquées que je vois ici, je pense que c'est plus qu'une bonne exception à faire au nom de l'aspect pratique. Surtout si vous vous assurez de garder les choses simples et de vous assurer que toute logique lourde (je dirais qu'il ne devrait vraiment pas y en avoir, mais les exigences diffèrent) se trouve dans une autre classe / couche à laquelle elle appartient. C'est certainement mieux que de polluer TOUS vos contrôleurs ou modèles pour une seule vue.

computrius
la source
2

Il existe une autre façon de l'archiver.

  1. Il suffit de mettre en œuvre la classe BaseController pour tous les contrôleurs .

  2. Dans la BaseControllerclasse, créez une méthode qui renvoie une classe Model comme par exemple.

public MenuPageModel GetTopMenu() 
{    

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

}
  1. Et dans la Layoutpage, vous pouvez appeler cette méthodeGetTopMenu()
@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>
Développeur
la source
0

Supposons que votre modèle soit une collection d'objets (ou peut-être un seul objet). Pour chaque objet du modèle, procédez comme suit.

1) Placez l'objet que vous souhaitez afficher dans le ViewBag. Par exemple:

  ViewBag.YourObject = yourObject;

2) Ajoutez une instruction using en haut de _Layout.cshtml qui contient la définition de classe pour vos objets. Par exemple:

@utilisation de YourApplication.YourClasses;

3) Lorsque vous référencez votreObjet dans _Layout, lancez-le. Vous pouvez appliquer la distribution en fonction de ce que vous avez fait dans (2).

HappyPawn8
la source
-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; }
}

Utilisez IContainsMyModel dans votre mise en page.

Résolu. Règle des interfaces.

Volonté
la source
1
Je ne sais pas pourquoi vous avez été voté à la baisse. Utiliser une interface, similaire à ce que vous avez fait ici, a fonctionné dans mon contexte.
costa
-6

Par exemple

@model IList<Model.User>

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

En savoir plus sur la nouvelle directive @model

Martin Fabik
la source
Mais que se passe-t-il si je veux passer le premier élément de la collection au modèle Layout?
SiberianGuy
Vous devez récupérer le premier élément de votre contrôleur et définir le modèle sur @model Model.User
Martin Fabik
Mais je veux que ma page reçoive IList et Layout - seulement le premier élément
SiberianGuy
Si je vous ai bien compris, vous voulez que le modèle soit un IList <SomeThing> et dans la vue obtenir le premier élément de la collection? Si c'est le cas, utilisez @ Model.First ()
Martin Fabik
6
L'affiche demandait comment passer un modèle à la page _Layout.cshtml ... pas à la vue principale qui utilise la mise en page.
Pure.Krome