web-dev-qa-db-fra.com

Passer des données à la mise en page qui sont communes à toutes les pages

J'ai un site web qui a une page de mise en page. Cependant, cette page de mise en page contient des données que tous les modèles de pages doivent fournir, telles que le titre de la page, le nom de la page et l'emplacement où nous nous trouvons pour un programme d'aide HTML que j'ai utilisé, qui effectue certaines actions. De plus, chaque page possède ses propres propriétés de modèles de vue.

Comment puis-je faire ceci? Il semble que ce soit une mauvaise idée de taper une mise en page, mais comment puis-je transmettre ces informations?

106
Rushino

Si vous devez transmettre les mêmes propriétés à chaque page, il serait judicieux de créer un modèle de vue de base utilisé par tous vos modèles de vue. Votre page de mise en page peut alors prendre ce modèle de base.

S'il existe une logique derrière ces données, vous devez la placer dans un contrôleur de base utilisé par tous vos contrôleurs.

Vous pouvez faire beaucoup de choses, l’approche importante étant de ne pas répéter le même code à plusieurs endroits.

Edit: Mise à jour à partir des commentaires ci-dessous

Voici un exemple simple pour démontrer le concept.

Créez un modèle de vue de base dont tous les modèles de vue hériteront.

public abstract class ViewModelBase
{
    public string Name { get; set; }
}

public class HomeViewModel : ViewModelBase
{
}

Votre page de mise en page peut prendre cela comme son modèle.

@model ViewModelBase
<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>Test</title>
    </head>
    <body>
        <header>
            Hello @Model.Name
        </header>
        <div>
            @this.RenderBody()
        </div>
    </body>
</html>

Enfin, définissez les données dans la méthode d'action.

public class HomeController
{
    public ActionResult Index()
    {
        return this.View(new HomeViewModel { Name = "Bacon" });
    }
}
130
Colin Bacon

J'ai utilisé RenderAction HTML helper pour le rasoir dans la mise en page. 

@{
   Html.RenderAction("Action", "Controller");
 }

J'en avais besoin pour une ficelle simple. Donc, mon action retourne string et l'écrit facilement. Mais si vous avez besoin de données complexes, vous pouvez renvoyer PartialViewResult et model.

 public PartialViewResult Action()
    {
        var model = someList;
        return PartialView("~/Views/Shared/_maPartialView.cshtml", model);
    }

Il vous suffit de mettre votre modèle au début de la vue partielle '_maPartialView.cshtml' que vous avez créée.

@model List<WhatEverYourObjeIs>

Vous pouvez ensuite utiliser les données du modèle dans cette vue partielle avec HTML.

57
Burk

Une autre option consiste à créer une classe LayoutModel séparée avec toutes les propriétés dont vous aurez besoin dans la présentation, puis à insérer une occurrence de cette classe dans ViewBag. J'utilise la méthode Controller.OnActionExecuting pour le remplir. Ensuite, au début de la présentation, vous pouvez extraire cet objet de ViewBag et continuer à accéder à cet objet fortement typé.

34
DenNukem

Vraisemblablement, le principal cas d'utilisation de cette opération est d'obtenir un modèle de base dans la vue pour toutes (ou la majorité) des actions du contrôleur.

Cela étant dit, j’ai utilisé une combinaison de plusieurs de ces réponses, tirant d’abord sur la réponse de Colin Bacon.

Il est vrai qu'il s'agit toujours d'une logique de contrôleur car nous alimentons un modèle de vue pour revenir à une vue. Ainsi, le bon endroit pour mettre cela est dans le contrôleur.

Nous voulons que cela se produise sur tous les contrôleurs, car nous l'utilisons pour la page de disposition. Je l'utilise pour les vues partielles qui sont rendues dans la page de présentation.

Nous souhaitons également bénéficier des avantages d’un ViewModel fortement typé.

Ainsi, j'ai créé un BaseViewModel et BaseController. Tous les contrôleurs ViewModels hériteront respectivement de BaseViewModel et BaseController.

Le code:

BaseController

public class BaseController : Controller
{
    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);

        var model = filterContext.Controller.ViewData.Model as BaseViewModel;

        model.AwesomeModelProperty = "Awesome Property Value";
        model.FooterModel = this.getFooterModel();
    }

    protected FooterModel getFooterModel()
    {
        FooterModel model = new FooterModel();
        model.FooterModelProperty = "OMG Becky!!! Another Awesome Property!";
    }
}

Notez l'utilisation de OnActionExecuted tel qu'il est extrait de this SO post

HomeController

public class HomeController : BaseController
{
    public ActionResult Index(string id)
    {
        HomeIndexModel model = new HomeIndexModel();

        // populate HomeIndexModel ...

        return View(model);
    }
}

BaseViewModel

public class BaseViewModel
{
    public string AwesomeModelProperty { get; set; }
    public FooterModel FooterModel { get; set; }
}

HomeViewModel

public class HomeIndexModel : BaseViewModel
{

    public string FirstName { get; set; }

    // other awesome properties
}

FooterModel

public class FooterModel
{
    public string FooterModelProperty { get; set; }
}

Layout.cshtml

@model WebSite.Models.BaseViewModel
<!DOCTYPE html>
<html>
<head>
    < ... meta tags and styles and whatnot ... >
</head>
<body>
    <header>
        @{ Html.RenderPartial("_Nav", Model.FooterModel.FooterModelProperty);}
    </header>

    <main>
        <div class="container">
            @RenderBody()
        </div>

        @{ Html.RenderPartial("_AnotherPartial", Model); }
        @{ Html.RenderPartial("_Contact"); }
    </main>

    <footer>
        @{ Html.RenderPartial("_Footer", Model.FooterModel); }
    </footer>

    < ... render scripts ... >

    @RenderSection("scripts", required: false)
</body>
</html>

_Nav.cshtml

@model string
<nav>
    <ul>
        <li>
            <a href="@Model" target="_blank">Mind Blown!</a>
        </li>
    </ul>
</nav>

Espérons que cela aide.

24
drizzie

Vous n'avez pas besoin de manipuler les actions ni de changer de modèle, utilisez simplement un contrôleur de base et convertissez le contrôleur existant à partir du contexte de la vue de présentation.

Créez un contrôleur de base avec les données communes souhaitées (titre/page/emplacement, etc.) et l'initialisation de l'action ... 

public abstract class _BaseController:Controller {
    public Int32 MyCommonValue { get; private set; }

    protected override void OnActionExecuting(ActionExecutingContext filterContext) {

        MyCommonValue = 12345;

        base.OnActionExecuting(filterContext);
    }
}

Assurez-vous que chaque contrôleur utilise le contrôleur de base ...

public class UserController:_BaseController {...

Convertissez le contrôleur de base existant à partir du contexte de la vue dans votre page _Layout.cshml ...

@{
    var myController = (_BaseController)ViewContext.Controller;
}

Vous pouvez maintenant faire référence aux valeurs de votre contrôleur de base à partir de votre page de présentation.

@myController.MyCommonValue
7
Carter Medlin

si vous voulez passer tout un modèle, faites comme si de la mise en page:

@model ViewAsModelBase
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=Edge">
    <meta charset="utf-8"/>
    <link href="/img/phytech_icon.ico" rel="shortcut icon" type="image/x-icon" />
    <title>@ViewBag.Title</title>
    @RenderSection("styles", required: false)    
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
    @RenderSection("scripts", required: false)
    @RenderSection("head", required: false)
</head>
<body>
    @Html.Action("_Header","Controller", new {model = Model})
    <section id="content">
        @RenderBody()
    </section>      
    @RenderSection("footer", required: false)
</body>
</html>

et ajoutez ceci dans le contrôleur:

public ActionResult _Header(ViewAsModelBase model)
4
Yakir Manor

Créer une vue de base qui représente le modèle de vue Mise en page est une approche terrible. Imaginez que vous souhaitiez avoir un modèle représentant la navigation définie dans la mise en page. Feriez-vous CustomersViewModel : LayoutNavigationViewModel? Pourquoi? Pourquoi devriez-vous transmettre les données du modèle de navigation à travers chaque modèle de vue que vous avez dans la solution?

Le modèle de vue Mise en page doit être dédié, seul, et ne doit pas forcer les autres modèles de vue à en dépendre.

Au lieu de cela, vous pouvez le faire dans votre fichier _Layout.cshtml:

@{ var model = DependencyResolver.Current.GetService<MyNamespace.LayoutViewModel>(); }

Plus important encore, nous n'avons pas besoin de new LayoutViewModel() et nous obtiendrons toutes les dépendances que LayoutViewModel a résolues pour nous.

par exemple.

public class LayoutViewModel
{
    private readonly DataContext dataContext;
    private readonly ApplicationUserManager userManager;

    public LayoutViewModel(DataContext dataContext, ApplicationUserManager userManager)
    {
    }
}
4
Hristo Yankov

D'autres réponses ont couvert à peu près tout ce qui concerne la façon dont nous pouvons passer de modèle à notre page de présentation. Mais j’ai trouvé un moyen d’utiliser différentes méthodes pour transmettre des variables à votre page de présentation de manière dynamique, sans utiliser de modèle ni de vue partielle. Disons que vous avez ce modèle - 

public class SubLocationsViewModel
{
    public string city { get; set; }
    public string state { get; set; }
}

Et vous voulez avoir la ville et l’état de manière dynamique. Par exemple 

dans votre index.cshtml vous pouvez mettre ces deux variables dans ViewBag

@model  MyProject.Models.ViewModel.SubLocationsViewModel
@{
    ViewBag.City = Model.city;
    ViewBag.State = Model.state;
}

Et puis dans votre layout.cshtml, vous pouvez accéder à ces variables de viewbag

<div class="text-wrap">
    <div class="heading">@ViewBag.City @ViewBag.State</div>
</div>
2
Chirag K

Je ne pense pas que ces réponses soient suffisamment souples pour une application de grande taille. Je ne suis pas fan de l'utilisation excessive du ViewBag, mais dans ce cas, par souci de flexibilité, je ferais une exception. Voici ce que je ferais ...

Vous devriez avoir un contrôleur de base sur tous vos contrôleurs. Ajoutez vos données de mise en page OnActionExecuting dans votre contrôleur de base (ou OnActionExecuted si vous souhaitez différer cela) ...

public class BaseController : Controller
{
    protected override void OnActionExecuting(ActionExecutingContext     
        filterContext)
    {
        ViewBag.LayoutViewModel = MyLayoutViewModel;
    }
}

public class HomeController : BaseController
{
    public ActionResult Index()
    {
        return View(homeModel);
    }
}

Ensuite, dans votre _Layout.cshtml, extrayez votre ViewModel du ViewBag ...

@{
  LayoutViewModel model = (LayoutViewModel)ViewBag.LayoutViewModel;
}

<h1>@model.Title</h1>

Ou...

<h1>@ViewBag.LayoutViewModel.Title</h1>

Cela n'interfère pas avec le codage des contrôleurs de votre page ou des modèles d'affichage.

1
Jack Sutherland

Pourquoi personne n'a suggéré de méthodes d'extension sur ViewData?

Option 1

Cela me semble de loin la solution la moins intrusive et la plus simple au problème. Pas de chaînes codées en dur. Aucune restriction imposée. Pas de code magique. Pas de code complexe.

public static class ViewDataExtensions
{
    private const string TitleData = "Title";
    public static void SetTitle<T>(this ViewDataDictionary<T> viewData, string value) => viewData[TitleData] = value;
    public static string GetTitle<T>(this ViewDataDictionary<T> viewData) => (string)viewData[TitleData] ?? "";
}

Définir les données dans la page

ViewData.SetTitle("abc");

Option 2

Une autre option, facilitant la déclaration de champ.

public static class ViewDataExtensions
{
    public static ViewDataField<string, V> Title<V>(this ViewDataDictionary<V> viewData) => new ViewDataField<string, V>(viewData, "Title", "");
}

public class ViewDataField<T,V>
{
    private readonly ViewDataDictionary<V> _viewData;
    private readonly string _field;
    private readonly T _defaultValue;

    public ViewDataField(ViewDataDictionary<V> viewData, string field, T defaultValue)
    {
        _viewData = viewData;
        _field = field;
        _defaultValue = defaultValue;
    }

    public T Value {
        get => (T)(_viewData[_field] ?? _defaultValue);
        set => _viewData[_field] = value;
    }
}

Définir les données dans la page. La déclaration est plus facile que la première option, mais la syntaxe d'utilisation est légèrement plus longue.

ViewData.Title().Value = "abc";

Option n ° 3

Ensuite, vous pouvez combiner cela avec le retour d'un seul objet contenant tous les champs liés à la présentation avec leurs valeurs par défaut.

public static class ViewDataExtensions
{
    private const string LayoutField = "Layout";
    public static LayoutData Layout<T>(this ViewDataDictionary<T> viewData) => 
        (LayoutData)(viewData[LayoutField] ?? (viewData[LayoutField] = new LayoutData()));
}

public class LayoutData
{
    public string Title { get; set; } = "";
}

Définir les données dans la page

var layout = ViewData.Layout();
layout.Title = "abc";

Cette troisième option a plusieurs avantages et je pense que c'est la meilleure option dans la plupart des cas:

  • Déclaration la plus simple des champs et des valeurs par défaut.

  • Syntaxe d'utilisation la plus simple lors de la configuration de plusieurs champs.

  • Permet de définir différents types de données dans ViewData (par exemple, Mise en page, En-tête, Navigation).

  • Permet un code et une logique supplémentaires dans la classe LayoutData.

P.S. N'oubliez pas d'ajouter l'espace de noms de ViewDataExtensions dans _ViewImports.cshtml

0
Etienne Charland

Il y a une autre façon de gérer ça. Peut-être pas la manière la plus propre du point de vue architectural, mais cela évite beaucoup de douleur liée aux autres réponses. Injectez simplement un service dans la structure Razor, puis appelez une méthode qui récupère les données nécessaires:

@inject IService myService

Puis plus tard dans la vue de présentation:

@if (await myService.GetBoolValue()) {
   // Good to go...
}

Encore une fois, l'architecture n'est pas claire (évidemment, le service ne doit pas être injecté directement dans la vue), mais le travail est fait.

0
Andrew

Vous pouvez également utiliser RenderSection , cela vous aide d’injecter vos données Model dans la vue _Layout.

Vous pouvez injecter des données View Model, Json, Script, CSS, HTML etc.

Dans cet exemple, j'injecte Json de ma vue Index à la vue Layout.

Index.chtml

@section commonLayoutData{

    <script>

        var products = @Html.Raw(Json.Encode(Model.ToList()));

    </script>

    }

_Layout.cshtml

@RenderSection("commonLayoutData", false)

Ceci élimine le besoin de créer un View Model de base séparé.

Hope aide quelqu'un.

0
stom