ASP.NET MVC Razor übergibt das Modell an das Layout

97

Was ich sehe, ist eine String-Layout-Eigenschaft. Aber wie kann ich ein Modell explizit an das Layout übergeben?

SiberianGuy
quelle
Ich habe mehrere Seiten mit unterschiedlichem Modell, aber dem gleichen Layout
SiberianGuy
2
Diese Stackoverflow-Frage scheint zu beantworten, was Sie fragen: stackoverflow.com/questions/13225315/…
Paul
Ich habe dieses Problem nicht. Modelist verfügbar in _Layout. Ich benutze MVC5.
Toddmo

Antworten:

66

Scheint, als hätten Sie Ihre Ansichtsmodelle etwas falsch modelliert, wenn Sie dieses Problem haben.

Persönlich würde ich niemals eine Layoutseite eingeben. Wenn Sie dies jedoch tun möchten, sollten Sie ein Basisansichtsmodell haben, von dem Ihre anderen Ansichtsmodelle erben, und Ihr Layout in das Basisansichtsmodell eingeben und Ihre Seiten einmal in das spezifische.

Mattias Jakobsson
quelle
11
"Ich persönlich würde niemals eine Layoutseite eingeben." Warum? Ich meine, wie gehen Sie mit seitendynamischen Inhalten um, die auf allen Seiten angezeigt werden? Überspringen Sie Controller aus der Ansicht? / Vielleicht möchten Sie RenderAction aus dem Layout verwenden? (Ich schaue es gerade an)
eglasius
52
@eglasius, Die Lösung, die ich verwende, hängt davon ab, über welche Art von Inhalten wir sprechen. Eine gängige Lösung ist jedoch die Verwendung von RenderAction, um Teile zu rendern, die ihre eigenen Daten auf der Layoutseite benötigen. Der Grund, warum ich die Layoutseite nicht gerne schreibe, ist, dass Sie gezwungen sind, in allen spezifischen Ansichtsmodellen immer ein "Basis" -Ansichtsmodell zu erben. Nach meiner Erfahrung ist dies normalerweise keine sehr gute Idee, und häufig treten Probleme auf, wenn es zu spät ist, das Design zu ändern (oder es dauert zu lange).
Mattias Jakobsson
2
Was ist, wenn ich das Basismodell durch Aggregation und nicht durch Vererbung einbeziehen möchte? Ein absolut legitimer Weg aus gestalterischer Sicht. Wie gehe ich dann mit dem Layout um?
Fjodor Soikin
4
Ich habe zwei Lösungen: ein generisches Modell für das Layout, damit ich MyLayoutModel <MyViewModel> für das Ansichtsmodell verwenden kann, wobei RenderPartial mit MyViewModel nur im Layout verwendet wird. Oder rendern Sie die Teile der Seite teilweise mit RenderAction für statisch zwischengespeicherte Teile und Ajax-Aufrufe für dynamische Teile. Aber ich bevorzuge die erste Lösung, da sie suchmaschinenfreundlicher ist und leicht mit Ajax-Updates kombiniert werden kann.
Softlion
4
Arbeiten an Legacy-Code, wo genau dies getan wurde. Es ist ein Albtraum. Geben Sie Ihre Layouts nicht ein ... bitte!
79
  1. Fügen Sie Ihrem Controller (oder Basis-Controller) eine Eigenschaft mit dem Namen MainLayoutViewModel (oder was auch immer) mit dem Typ hinzu, den Sie verwenden möchten.
  2. Instanziieren Sie im Konstruktor Ihres Controllers (oder Basis-Controllers) den Typ und setzen Sie ihn auf die Eigenschaft.
  3. Stellen Sie es auf das ViewData-Feld (oder ViewBag) ein.
  4. Übertragen Sie diese Eigenschaft auf der Seite Layout in Ihren Typ.

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

}

Beispiel oben auf der Layoutseite

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

Jetzt können Sie auf die Variable 'viewModel' auf Ihrer Layoutseite mit vollem Zugriff auf das eingegebene Objekt verweisen.

Ich mag diesen Ansatz, weil es der Controller ist, der das Layout steuert, während die einzelnen Seitenansichtsmodelle layoutunabhängig bleiben.

Hinweise für MVC Core


Mvc Core scheint den Inhalt von ViewData / ViewBag beim ersten Aufruf jeder Aktion wegzublasen. Dies bedeutet, dass das Zuweisen von ViewData im Konstruktor nicht funktioniert. Was jedoch funktioniert, ist die Verwendung von IActionFilterund genau die gleiche Arbeit in OnActionExecuting. Zieh MyActionFilterdeine an 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
quelle
1
Ich verstehe ... aber Dynamik / Casts sind ziemlich zentral für Rasiermesserseiten. Eine Möglichkeit besteht darin, MainLayoutViewModel eine statische Methode hinzuzufügen, die das Casting für Sie ausführt (z. B. MainLayoutViewModel.FromViewBag (this.ViewBag)), damit zumindest das Casting an einem Ort stattfindet und Sie dort besser mit Ausnahmen umgehen können.
BlackjacketMack
@BlackjacketMack Guter Ansatz und ich habe ihn mit dem oben genannten erreicht und einige Modifikationen vorgenommen, da ich eine andere Anforderung hatte und dies hat mir wirklich geholfen, danke. Können wir das gleiche mit TempData erreichen, wenn ja, wie und nein, dann bitte sagen Sie mir, warum es nicht verwendet werden kann. Danke noch einmal.
Zaker
2
@User - TempData verwendet Session und fühlt sich für mich immer etwas klobig an. Mein Verständnis ist, dass es "einmal gelesen" ist, so dass es, sobald Sie es lesen, aus der Sitzung entfernt wird (oder vielleicht sobald die Anfrage beendet ist). Es ist möglich, dass Sie eine Sitzung in SQL Server (oder Dynamo Db) speichern. Berücksichtigen Sie daher die Tatsache, dass Sie das MasterLayoutViewModel serialisieren müssen ... nicht das, was Sie höchstwahrscheinlich möchten. Wenn Sie es also auf ViewData setzen, wird es in einem kleinen flexiblen Wörterbuch gespeichert, das genau der Rechnung entspricht.
BlackjacketMack
Ganz einfach, ich habe Ihre Lösung verwendet, aber ich bin neu bei MVC. Ich frage mich nur, ob dies eine gute Vorgehensweise ist. oder zumindest nicht schlecht?
Karim AG
1
Hallo Karim AG, ich denke es ist ein bisschen von beidem. Ich neige dazu, das Speichern von Inhalten in ViewData als schlechte Vorgehensweise zu betrachten (es ist schwer zu verfolgen, wörterbuchbasiert, nicht wirklich typisiert) ... ABER ... das Eingeben all Ihrer Layout-Eigenschaften in ein stark typisiertes Objekt ist eine großartige Vorgehensweise. Also mache ich einen Kompromiss, indem ich sage, ok, lass uns eine Sache dort speichern, aber den Rest davon auf ein gutes, stark typisiertes ViewModel beschränken.
BlackjacketMack
30

Dies ist ziemlich grundlegend. Alles, was Sie tun müssen, ist, ein Basisansichtsmodell zu erstellen und sicherzustellen, dass ALLE! und ich meine ALL! Von Ihren Ansichten, die jemals dieses Layout verwenden werden, erhalten Sie Ansichten, die dieses Basismodell verwenden!

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

public class EmptyViewModel : ViewModelBase
{
}

public abstract class ViewModelBase
{
}

in der _Layout.cshtml:

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

in der Index-Methode (zum Beispiel) im Home-Controller:

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

die Index.cshtml:

@model Models.SomeViewModel

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

<div class="row">

Ich bin nicht der Meinung, dass das Übergeben eines Modells an das _layout ein Fehler ist. Einige Benutzerinformationen können übergeben werden und die Daten können in die Vererbungskette des Controllers eingetragen werden, sodass nur eine Implementierung erforderlich ist.

Für fortgeschrittenere Zwecke sollten Sie natürlich in Betracht ziehen, einen benutzerdefinierten statischen Kontraxt mithilfe von Injection zu erstellen und diesen Modell-Namespace in die Datei _Layout.cshtml aufzunehmen.

Aber für einfache Benutzer reicht dies aus

Yakir Manor
quelle
Ich stimme mit Ihnen ein. Vielen Dank.
Sebastián Guerrero
1
up und möchte nur erwähnen, dass es auch mit einer Schnittstelle anstelle der Basisklasse
funktioniert
27

Eine übliche Lösung besteht darin, ein Basisansichtsmodell zu erstellen, das die in der Layoutdatei verwendeten Eigenschaften enthält, und dann vom Basismodell an die auf den jeweiligen Seiten verwendeten Modelle zu erben.

Das Problem bei diesem Ansatz ist, dass Sie sich jetzt auf das Problem eines Modells festgelegt haben, das nur von einer anderen Klasse erben kann, und Ihre Lösung möglicherweise so ist, dass Sie die Vererbung für das von Ihnen beabsichtigte Modell sowieso nicht verwenden können.

Meine Lösung beginnt auch mit einem Basisansichtsmodell:

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

    public string Title { get;}
}

Was ich dann benutze, ist eine generische Version des LayoutModels, die vom LayoutModel erbt, wie folgt:

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

    public T PageModel { get; }
}

Mit dieser Lösung habe ich die Notwendigkeit einer Vererbung zwischen dem Layoutmodell und dem Modell getrennt.

Jetzt kann ich das LayoutModel in Layout.cshtml folgendermaßen verwenden:

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

Und auf einer Seite können Sie das generische LayoutModel wie folgt verwenden:

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

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

Von Ihrem Controller geben Sie einfach ein Modell vom Typ LayoutModel zurück:

public ActionResult Page()
{
    return View(new LayoutModel<Customer>(new Customer() { Name = "Test" }, "Title");
}
Oskar Sjöberg
quelle
1
Bonus für den Hinweis auf das Problem der Mehrfachvererbung und den Umgang damit! Dies ist eine bessere Antwort für die Skalierbarkeit.
Brett Spencer
1
Beste Lösung meiner Meinung nach. Aus architektonischer Sicht ist es skalierbar und wartbar. Dies ist der richtige Weg, dies zu tun. Ich mochte ViewBag oder ViewData nie ..... Beide scheinen mir hackig zu sein.
Jonathan Alfaro
10

Warum fügen Sie nicht einfach eine neue Teilansicht hinzu, wobei i's spezifischer Controller das erforderliche Modell an die Teilansicht übergibt und schließlich die erwähnte Teilansicht in Ihrer Layout.cshtml mit RenderPartial oder RenderAction rendert?

Ich benutze diese Methode, um die angemeldeten Benutzerinformationen wie Name, Profilbild usw. anzuzeigen.

Arya Sh
quelle
2
Können Sie das bitte näher erläutern? Ich würde mich über einen Link zu einem Blog-Beitrag
freuen, der
Das kann funktionieren, aber warum sollte man den Performance-Hit nehmen? Sie müssen auf die gesamte vom Controller durchgeführte Verarbeitung warten und die Ansicht zurückgeben, damit der Browser des Benutzers eine weitere Anforderung zum Abrufen der benötigten Daten stellt. Und was ist, wenn Ihr Layout von den Daten abhängt, die ordnungsgemäß gerendert werden sollen? IMHO ist dies keine Antwort auf diese Frage.
Brett Spencer
3

alte Frage, aber nur um die Lösung für MVC5-Entwickler zu erwähnen, können Sie die verwenden Model Eigenschaft wie in der Ansicht verwenden.

Die ModelEigenschaft in Ansicht und Layout ist demselben ViewDataDictionaryObjekt zugeordnet, sodass Sie keine zusätzlichen Arbeiten ausführen müssen, um Ihr Modell an die Layoutseite zu übergeben, und Sie müssen nicht @model MyModelNameim Layout deklarieren .

@Model.XXXBeachten Sie jedoch, dass bei Verwendung im Layout das IntelliSense-Kontextmenü nicht angezeigt wird, da es sich Modelhier um ein dynamisches Objekt handelt ViewBag.

Laz Ziya
quelle
2

Vielleicht ist es technisch nicht die richtige Art, damit umzugehen, aber die einfachste und vernünftigste Lösung für mich besteht darin, einfach eine Klasse zu erstellen und sie im Layout zu instanziieren. Dies ist eine einmalige Ausnahme von der ansonsten korrekten Vorgehensweise. Wenn dies mehr als im Layout erfolgt, müssen Sie Ihre Arbeitsweise ernsthaft überdenken und möglicherweise einige weitere Tutorials lesen, bevor Sie mit Ihrem Projekt fortfahren.

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

dann in der Ansicht

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

In .net Core können Sie dies sogar überspringen und die Abhängigkeitsinjektion verwenden.

@inject My.Namespace.IMyLayoutModel myLayoutModel

Es ist einer dieser Bereiche, der etwas schattig ist. Aber angesichts der extrem komplizierten Alternativen, die ich hier sehe, denke ich, dass es mehr als eine gute Ausnahme ist, im Namen der Praktikabilität zu machen. Besonders wenn Sie sicherstellen, dass es einfach bleibt und dass jede schwere Logik (ich würde argumentieren, dass es eigentlich keine geben sollte, aber die Anforderungen unterschiedlich sind) in einer anderen Klasse / Schicht liegt, zu der sie gehört. Es ist sicherlich besser, als ALLE Ihre Controller oder Modelle zu verschmutzen, um im Grunde nur eine Ansicht zu erhalten.

computrius
quelle
2

Es gibt eine andere Möglichkeit, es zu archivieren.

  1. Implementieren Sie einfach die BaseController-Klasse für alle Controller .

  2. BaseControllerErstellen Sie in der Klasse eine Methode, die beispielsweise eine Model-Klasse zurückgibt.

public MenuPageModel GetTopMenu() 
{    

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

}
  1. Und auf der LayoutSeite können Sie diese Methode aufrufenGetTopMenu()
@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>
Entwickler
quelle
0

Angenommen, Ihr Modell ist eine Sammlung von Objekten (oder möglicherweise ein einzelnes Objekt). Führen Sie für jedes Objekt im Modell die folgenden Schritte aus.

1) Legen Sie das Objekt, das Sie anzeigen möchten, in den ViewBag. Beispielsweise:

  ViewBag.YourObject = yourObject;

2) Fügen Sie oben in _Layout.cshtml eine using-Anweisung hinzu, die die Klassendefinition für Ihre Objekte enthält. Beispielsweise:

@ using YourApplication.YourClasses;

3) Wenn Sie in _Layout auf Ihr Objekt verweisen, besetzen Sie es. Sie können die Besetzung aufgrund dessen anwenden, was Sie in (2) getan haben.

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

Verwenden Sie IContainsMyModel in Ihrem Layout.

Gelöst. Schnittstellenregel.

Wille
quelle
1
Ich bin mir nicht sicher, warum du herabgestimmt wurdest. Die Verwendung einer Benutzeroberfläche, ähnlich der, die Sie hier ausgeführt haben, hat in meinem Kontext funktioniert.
Costa
-6

Beispielsweise

@model IList<Model.User>

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

Lesen Sie mehr über die neue @ model- Direktive

Martin Fabik
quelle
Was aber, wenn ich das erste Element der Sammlung an das Layoutmodell übergeben möchte?
SiberianGuy
Sie müssen das erste Element in Ihrem Controller abrufen und das Modell auf @model Model.User
Martin Fabik
Aber ich möchte, dass meine Seite IList und Layout erhält - nur das erste Element
SiberianGuy
Wenn ich Sie richtig verstanden habe, möchten Sie, dass das Modell eine IList <SomeThing> ist und in der Ansicht das erste Element der Sammlung erhält? Wenn ja, verwenden Sie @ Model.First ()
Martin Fabik
6
Auf dem Poster wurde gefragt, wie ein Modell an die Seite _Layout.cshtml übergeben werden soll. Nicht an die Hauptansicht, in der das Layout verwendet wird.
Pure.Krome