it-swarm.com.de

Übergeben Sie Daten an ein Layout, das allen Seiten gemeinsam ist

Ich habe eine Website, die eine Layoutseite hat. Diese Layoutseite enthält jedoch Daten, die in jedem Seitenmodell wie Seitentitel, Seitenname und dem Ort angegeben werden müssen, an dem wir uns tatsächlich für einen HTML-Helfer befinden, den ich ausgeführt habe, um eine Aktion auszuführen. Außerdem hat jede Seite ihre eigenen Ansichtsmodelleigenschaften.

Wie kann ich das machen? Es scheint eine schlechte Idee zu sein, ein Layout einzugeben, aber wie gebe ich diese Informationen weiter?

106
Rushino

Wenn Sie für jede Seite dieselben Eigenschaften übergeben müssen, ist es sinnvoll, ein Basis-Ansichtsmodell zu erstellen, das von allen Ihren Ansichtsmodellen verwendet wird. Ihre Layoutseite kann dann dieses Basismodell übernehmen.

Wenn hinter diesen Daten eine Logik erforderlich ist, sollten Sie diese in einen Basiscontroller stecken, der von allen Ihren Controllern verwendet wird.

Es gibt viele Dinge, die Sie tun können, wobei es wichtig ist, denselben Code nicht an mehreren Stellen zu wiederholen.

Bearbeiten: Update von Kommentaren unten

Hier ist ein einfaches Beispiel, um das Konzept zu demonstrieren.

Erstellen Sie ein Basisansichtsmodell, von dem alle Ansichtsmodelle erben.

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

public class HomeViewModel : ViewModelBase
{
}

Ihre Layoutseite kann dies als Modell übernehmen.

@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>

Legen Sie schließlich die Daten in der Aktionsmethode fest.

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

Ich habe RenderAction-HTML-Helfer für Rasierer im Layout verwendet. 

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

Ich brauchte es für eine einfache Saite. Meine Aktion gibt also String zurück und schreibt ihn einfach in der Ansicht auf. Wenn Sie jedoch komplexe Daten benötigen, können Sie PartialViewResult und das Modell zurückgeben.

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

Sie müssen lediglich Ihr Modell mit der von Ihnen erstellten Teilansicht '_maPartialView.cshtml' beginnen

@model List<WhatEverYourObjeIs>

Dann können Sie Daten in dem Modell in dieser Teilansicht mit HTML verwenden.

57
Burk

Eine andere Option besteht darin, eine separate LayoutModel-Klasse mit allen Eigenschaften zu erstellen, die Sie für das Layout benötigen, und dann eine Instanz dieser Klasse in ViewBag zu füllen. Ich benutze Controller.OnActionExecuting-Methode, um es aufzufüllen. Anschließend können Sie dieses Objekt am Anfang des Layouts von ViewBag zurückziehen und weiterhin auf dieses stark typisierte Objekt zugreifen.

34
DenNukem

Vermutlich besteht der hauptsächliche Anwendungsfall dafür, ein Basismodell für alle (oder die Mehrheit der) Controller-Aktionen in die Ansicht zu bringen.

In Anbetracht dessen habe ich eine Kombination aus mehreren dieser Antworten verwendet, wobei Colin Bacons Antwort primär als Huckepack dient.

Es ist richtig, dass dies immer noch Controllerlogik ist, da wir ein Viewmodel füllen, um zu einer Ansicht zurückzukehren. Der richtige Ort, um dies zu platzieren, ist also in der Steuerung.

Wir möchten, dass dies auf allen Controllern geschieht, weil wir dies für die Layoutseite verwenden. Ich verwende es für Teilansichten, die auf der Layoutseite gerendert werden.

Wir möchten auch noch den zusätzlichen Vorteil eines stark typisierten ViewModels

Daher habe ich ein BaseViewModel und einen BaseController erstellt. Alle ViewModels-Controller erben jeweils von BaseViewModel und BaseController.

Der 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!";
    }
}

Beachten Sie die Verwendung von OnActionExecuted aus 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>

Hoffentlich hilft das.

24
drizzie

Sie müssen sich nicht mit Aktionen herumschlagen oder das Modell ändern. Verwenden Sie einfach einen Basis-Controller und konvertieren Sie den vorhandenen Controller aus dem Layout-View-Kontext.

Erstellen Sie einen Basiscontroller mit den gewünschten gemeinsamen Daten (Titel/Seite/Speicherort usw.) und Aktionsinitialisierung ... 

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

    protected override void OnActionExecuting(ActionExecutingContext filterContext) {

        MyCommonValue = 12345;

        base.OnActionExecuting(filterContext);
    }
}

Stellen Sie sicher, dass jeder Controller den Basiscontroller verwendet ...

public class UserController:_BaseController {...

Konvertieren Sie den vorhandenen Basiscontroller aus dem Ansichtskontext in Ihrer _Layout.cshml-Seite ...

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

Jetzt können Sie von Ihrer Layoutseite auf Werte in Ihrem Basiscontroller verweisen.

@myController.MyCommonValue
7
Carter Medlin

wenn Sie ein gesamtes Modell übergeben möchten, gehen Sie wie im Layout vor:

@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>

und füge dies im Controller hinzu:

public ActionResult _Header(ViewAsModelBase model)
4
Yakir Manor

Das Erstellen einer Basisansicht, die das Layout-Ansichtsmodell darstellt, ist ein schrecklicher Ansatz. Stellen Sie sich vor, Sie möchten ein Modell haben, das die im Layout definierte Navigation darstellt. Würden Sie CustomersViewModel : LayoutNavigationViewModel tun? Warum? Warum sollten Sie die Navigationsmodelldaten durch jedes einzelne Ansichtsmodell übergeben, das Sie in der Lösung haben?

Das Layout-Ansichtsmodell sollte eigenständig sein und sollte den Rest der Ansichtsmodelle nicht dazu zwingen, davon abhängig zu sein.

Stattdessen können Sie dies in Ihrer _Layout.cshtml-Datei tun:

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

Am wichtigsten ist, dass wir nicht new LayoutViewModel() brauchen und wir werden all die Abhängigkeiten bekommen, die LayoutViewModel für uns gelöst hat.

z.B.

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

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

Andere Antworten haben so ziemlich alles behandelt, wie wir das Modell an unsere Layoutseite übergeben können. Ich habe jedoch einen Weg gefunden, wie Sie Variablen dynamisch an Ihre Layoutseite übergeben können, ohne ein Modell oder eine Teilansicht in Ihrem Layout zu verwenden. Nehmen wir an, Sie haben dieses Modell - 

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

Und Sie wollen Stadt und Staat dynamisch bekommen. Z.B. 

in Ihrer index.cshtml können Sie diese beiden Variablen in ViewBag einfügen

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

Und dann können Sie in Ihrer layout.cshtml auf diese Viewbag-Variablen zugreifen

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

Ich denke nicht, dass eine dieser Antworten für eine große Anwendung auf Unternehmensebene flexibel genug ist. Ich bin kein Fan von Überbeanspruchung des ViewBag, aber in diesem Fall würde ich aus Gründen der Flexibilität eine Ausnahme machen. Folgendes würde ich tun ...

Sie sollten auf allen Controllern einen Basiscontroller haben. Fügen Sie Ihre Layoutdaten OnActionExecuting in Ihrem Basiscontroller hinzu (oder OnActionExecuted, wenn Sie dies verzögern möchten).

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

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

Dann ziehen Sie in Ihrer _Layout.cshtml Ihr ViewModel aus dem ViewBag ...

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

<h1>@model.Title</h1>

Oder...

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

Dadurch wird die Codierung für die Controller Ihrer Seite oder die Ansichtsmodelle nicht beeinträchtigt.

1
Jack Sutherland

Warum hat niemand Erweiterungsmethoden für ViewData vorgeschlagen?

Option 1

Scheint mir die mit Abstand am wenigsten aufdringliche und einfachste Lösung des Problems zu sein. Keine fest codierten Zeichenfolgen. Keine auferlegten Beschränkungen. Keine magische Kodierung. Kein komplexer Code.

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] ?? "";
}

Legen Sie die Daten auf der Seite fest

ViewData.SetTitle("abc");

Option 2

Eine weitere Option, die die Felddeklaration erleichtert.

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

Legen Sie die Daten auf der Seite fest. Die Deklaration ist einfacher als die erste Option, die Syntax für die Verwendung ist jedoch etwas länger.

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

Option 3

Dann können Sie dies mit der Rückgabe eines einzelnen Objekts kombinieren, das alle layoutbezogenen Felder mit ihren Standardwerten enthält.

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

Legen Sie die Daten auf der Seite fest

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

Diese dritte Option hat mehrere Vorteile und ist meiner Meinung nach in den meisten Fällen die beste Option:

  • Einfachste Deklaration von Feldern und Standardwerten.

  • Einfachste Verwendungssyntax beim Festlegen mehrerer Felder.

  • Ermöglicht das Festlegen verschiedener Arten von Daten in ViewData (z. B. Layout, Kopfzeile, Navigation).

  • Ermöglicht zusätzlichen Code und zusätzliche Logik in der LayoutData-Klasse.

P.S. Vergessen Sie nicht, den Namespace von ViewDataExtensions in _ViewImports.cshtml hinzuzufügen

0

Es gibt eine andere Möglichkeit, damit umzugehen. Aus architektonischer Sicht vielleicht nicht der sauberste Weg, aber es vermeidet eine Menge Schmerzen, die mit den anderen Antworten verbunden sind. Fügen Sie einfach einen Service in das Razor-Layout ein und rufen Sie dann eine Methode auf, die die erforderlichen Daten abruft:

@inject IService myService

Dann später in der Layoutansicht:

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

Auch hier ist die Architektur nicht sauber (offensichtlich sollte der Service nicht direkt in die Ansicht eingebunden werden), aber die Arbeit wird erledigt.

0
Andrew

Sie können auch RenderSection verwenden. Dies hilft Ihnen, Ihre Model-Daten in die _Layout-Ansicht einzufügen.

Sie können View Model Daten, Json, Script, CSS, HTML usw

In diesem Beispiel füge ich Json aus meiner Index-Ansicht in Layout-Ansicht ein.

Index.chtml

@section commonLayoutData{

    <script>

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

    </script>

    }

_Layout.cshtml

@RenderSection("commonLayoutData", false)

Dadurch muss keine separate Basis View Model erstellt werden.

Hoffnung hilft jemandem.

0
stom