ASP.NET MVC Razor przekazuje model do układu


Odpowiedzi:


66

Wygląda na to, że modelowałeś swoje modele widoków trochę źle, jeśli masz ten problem.

Osobiście nigdy nie napisałbym strony układu. Ale jeśli chcesz to zrobić, powinieneś mieć podstawowy model widoku, z którego dziedziczą inne modele widoku, i wpisz układ do podstawowego modelu widoku, a strony do określonego raz.


11
„Osobiście nigdy nie napisałbym strony układu”. Czemu? Mam na myśli, jak radzisz sobie z boczną dynamiczną zawartością, która pojawia się na wszystkich stronach? Pomijasz kontrolery z widoku? / może masz na myśli użycie RenderAction z układu? (Właśnie na to patrzę)
eglasius

52
@eglasius, Rozwiązanie, z którego korzystam, różni się w zależności od rodzaju treści, o których mówimy. Ale typowym rozwiązaniem jest użycie RenderAction do renderowania części, które wymagają własnych danych na stronie układu. Powodem, dla którego nie lubię pisać strony układu, jest to, że wymusza to zawsze dziedziczenie „bazowego” modelu widoku we wszystkich określonych modelach widoku. Z mojego doświadczenia wynika, że ​​zwykle nie jest to zbyt dobry pomysł i często będziesz mieć problemy, gdy będzie za późno na zmianę projektu (lub zajmie to dużo czasu).
Mattias Jakobsson,

2
A jeśli chcę uwzględnić model podstawowy przez agregację, a nie dziedziczenie? W pełni uzasadniony sposób z punktu widzenia projektowania. Jak więc mam obsłużyć układ?
Fyodor Soikin

4
Mam 2 rozwiązania: ogólny model układu, dzięki czemu mogę użyć MyLayoutModel <MyViewModel> dla modelu widoku, używając RenderPartial z MyViewModel tylko w układzie. Lub częściowo wyrenderuj części strony za pomocą RenderAction dla statycznych części w pamięci podręcznej i wywołań ajax dla części dynamicznych. Ale wolę pierwsze rozwiązanie, ponieważ jest bardziej przyjazne dla wyszukiwarek i można je łatwo łączyć z aktualizacjami Ajax.
Softlion

4
Praca nad starszym kodem, gdzie dokładnie to zostało zrobione. To koszmar. Nie wpisuj swoich układów ... proszę!

79
  1. Dodaj właściwość do kontrolera (lub kontrolera podstawowego) o nazwie MainLayoutViewModel (lub cokolwiek) z dowolnym typem, którego chcesz użyć.
  2. W konstruktorze kontrolera (lub kontrolera podstawowego) utwórz wystąpienie typu i ustaw go na właściwość.
  3. Ustaw go na pole ViewData (lub ViewBag)
  4. Na stronie Układ rzutuj tę właściwość na swój typ.

Przykład: kontroler:

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

}

Przykładowa góra strony układu

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

Teraz możesz odwołać się do zmiennej „viewModel” na swojej stronie układu z pełnym dostępem do wpisanego obiektu.

Podoba mi się to podejście, ponieważ to kontroler kontroluje układ, podczas gdy poszczególne modele widoku strony pozostają niezależne od układu.

Uwagi dotyczące MVC Core


Wydaje się, że Mvc Core wysadza zawartość ViewData / ViewBag po pierwszym wywołaniu każdej akcji. Oznacza to, że przypisywanie ViewData w konstruktorze nie działa. Jednak to, co działa, to używanie IActionFilteri wykonywanie dokładnie tej samej pracy w OnActionExecuting. Załóż MyActionFilterswój 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
Rozumiem ... ale dynamika / odlewy są kluczowe dla stron z ostrzami. Jedną z rzeczy, które możesz zrobić, to dodać statyczną metodę do MainLayoutViewModel, która wykonuje rzutowanie za Ciebie (np. MainLayoutViewModel.FromViewBag (this.ViewBag)), aby przynajmniej rzutowanie odbywało się w jednym miejscu i tam możesz lepiej obsługiwać wyjątki.
BlackjacketMack

@BlackjacketMack Dobre podejście i osiągnąłem to korzystając z powyższego i dokonując modyfikacji bcoz miałem wymaganie diff i to naprawdę pomogło mi dzięki. Czy możemy osiągnąć to samo za pomocą TempData, jeśli tak, to jak i nie, to proszę mi powiedzieć, dlaczego nie można go użyć. Dzięki jeszcze raz.
Zaker

2
@User - TempData używa sesji i zawsze wydaje mi się trochę niezdarna. Rozumiem, że jest to opcja „do odczytu raz”, więc gdy tylko ją przeczytasz, usuwa ją z sesji (lub być może zaraz po zakończeniu żądania). Możliwe, że przechowujesz sesję w Sql Server (lub Dynamo Db), więc weź pod uwagę fakt, że musiałbyś serializować MasterLayoutViewModel ... a nie to, co najprawdopodobniej chcesz. Zasadniczo ustawienie go na ViewData przechowuje go w pamięci w trochę elastycznym słowniku, który pasuje do rachunku.
BlackjacketMack

Dość proste, korzystałem z twojego rozwiązania, ale jestem nowy w MVC, więc zastanawiam się, czy jest to uważane za dobrą praktykę? a przynajmniej nie jest zły?
Karim AG

1
Cześć Karim AG, myślę, że to trochę z obu. Zwykle uważam przechowywanie rzeczy w ViewData za złą praktykę (jest to trudne do śledzenia, oparte na słownikach, nie do końca wpisywane na klawiaturze) ... ALE ... wpisywanie wszystkich właściwości układu w silnie wpisanym obiekcie to świetna praktyka. Więc idę na kompromis, mówiąc: ok, zapiszmy tam jedną rzecz, ale resztę zamknijmy w dobrym, silnie wpisanym ViewModelu.
BlackjacketMack

30

to jest dość podstawowa rzecz, wszystko, co musisz zrobić, to utworzyć model widoku podstawowego i upewnić się, że WSZYSTKIE! i mam na myśli WSZYSTKIE! Twoich widoków, które kiedykolwiek będą używać tego układu, otrzymają widoki korzystające z tego modelu podstawowego!

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

public class EmptyViewModel : ViewModelBase
{
}

public abstract class ViewModelBase
{
}

w _Layout.cshtml:

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

w metodzie Index (na przykład) w kontrolerze domowym:

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

plik Index.cshtml:

@model Models.SomeViewModel

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

<div class="row">

Nie zgadzam się, że przekazanie modelu do _layout jest błędem, niektóre informacje o użytkowniku mogą zostać przekazane, a dane mogą zostać wypełnione w łańcuchu dziedziczenia kontrolerów, więc potrzebna jest tylko jedna implementacja.

oczywiście dla bardziej zaawansowanych celów należy rozważyć utworzenie niestandardowego kontekstu statycznego przy użyciu wstrzykiwania i dołączyć tę przestrzeń nazw modelu do pliku _Layout.cshtml.

ale dla zwykłych użytkowników to wystarczy


Zgadzam się z Tobą. Dzięki.
Sebastián Guerrero,

1
up i chcę tylko wspomnieć, że działa również z interfejsem zamiast klasy bazowej
VladL,

27

Typowym rozwiązaniem jest utworzenie modelu widoku bazowego, który zawiera właściwości używane w pliku układu, a następnie dziedziczenie z modelu podstawowego do modeli używanych na odpowiednich stronach.

Problem z tym podejściem polega na tym, że teraz zamknąłeś się w problemie, że model może dziedziczyć tylko z jednej innej klasy, a być może Twoje rozwiązanie jest takie, że nie możesz i tak używać dziedziczenia w modelu, który zamierzałeś.

Moje rozwiązanie również zaczyna się od modelu widoku podstawowego:

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

    public string Title { get;}
}

Następnie używam ogólnej wersji LayoutModel, która dziedziczy po LayoutModel, na przykład:

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

    public T PageModel { get; }
}

Dzięki temu rozwiązaniu wyłączyłem konieczność dziedziczenia między modelem układu a modelem.

Więc teraz mogę iść dalej i użyć LayoutModel w Layout.cshtml w następujący sposób:

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

Na stronie możesz użyć ogólnego modelu LayoutModel w następujący sposób:

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

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

Ze swojego kontrolera po prostu zwracasz model typu LayoutModel:

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

1
Bonus za wskazanie problemu wielokrotnego dziedziczenia i jak sobie z nim poradzić! To lepsza odpowiedź na skalowalność.
Brett Spencer

1
Najlepsze moim zdaniem rozwiązanie. Z architektonicznego punktu widzenia jest skalowalny i łatwy w utrzymaniu. To jest właściwy sposób na zrobienie tego. Nigdy nie lubiłem ViewBag ani ViewData ..... Oba wydają mi się dziwaczne.
Jonathan Alfaro

10

Dlaczego po prostu nie dodasz nowego widoku częściowego z własnym, określonym kontrolerem, przekazując wymagany model do widoku częściowego i ostatecznie renderuj wspomniany widok częściowy na swoim Layout.cshtml za pomocą RenderPartial lub RenderAction?

Używam tej metody do wyświetlania informacji o zalogowanym użytkowniku, takich jak imię i nazwisko, zdjęcie profilowe itp.


2
Czy możesz to rozwinąć? Byłbym wdzięczny za link do jakiegoś posta na blogu, który przechodzi przez tę technikę
J86,

To może zadziałać, ale po co uderzyć w wydajność? Musisz zaczekać na całe przetwarzanie dokonane przez administratora, zwrócić widok, tylko po to, aby przeglądarka użytkownika wysłała KOLEJNE żądanie uzyskania potrzebnych danych. A co, jeśli Twój układ zależy od danych, aby odpowiednio renderować. IMHO to nie jest odpowiedź na to pytanie.
Brett Spencer

3

stare pytanie, ale żeby wspomnieć o rozwiązaniu dla programistów MVC5, możesz użyć tej Modelsamej właściwości co w widoku.

ModelNieruchomość zarówno widok i układ jest assosiated z tego samego ViewDataDictionaryobiektu, więc nie trzeba wykonywać żadnych dodatkowych prac przekazać swój model na stronie układu, a nie trzeba zadeklarować @model MyModelNamew układzie.

Zauważ jednak, że gdy używasz @Model.XXXw układzie, menu kontekstowe intelliSense nie pojawi się, ponieważ Modeltutaj jest obiektem dynamicznym, tak jak ViewBag.


2

Może z technicznego punktu widzenia nie jest to właściwy sposób radzenia sobie z tym, ale dla mnie najprostszym i najbardziej rozsądnym rozwiązaniem jest utworzenie klasy i utworzenie jej instancji w układzie. Jest to jednorazowy wyjątek od prawidłowego sposobu wykonania tego. Jeśli jest to zrobione częściej niż w układzie, musisz poważnie przemyśleć to, co robisz, i może przeczytać jeszcze kilka samouczków, zanim przejdziesz dalej w swoim projekcie.

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

wtedy w widoku

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

w .net core można to nawet pominąć i użyć iniekcji zależności.

@inject My.Namespace.IMyLayoutModel myLayoutModel

To jeden z tych obszarów, które są trochę zacienione. Ale biorąc pod uwagę niezwykle skomplikowane alternatywy, które tutaj widzę, myślę, że jest to więcej niż dobry wyjątek w imię praktyczności. Zwłaszcza jeśli upewnisz się, że jest to proste i upewnij się, że każda ciężka logika (argumentowałbym, że tak naprawdę nie powinno być, ale wymagania się różnią) znajduje się w innej klasie / warstwie, do której należy. Jest to z pewnością lepsze niż zanieczyszczanie WSZYSTKICH kontrolerów lub modeli ze względu na w zasadzie tylko jeden widok.


2

Istnieje inny sposób archiwizacji.

  1. Wystarczy zaimplementować klasę BaseController dla wszystkich kontrolerów .

  2. W BaseControllerklasie utwórz metodę, która zwraca klasę Model, jak na przykład.

public MenuPageModel GetTopMenu() 
{    

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

}
  1. A na Layoutstronie możesz wywołać tę metodęGetTopMenu()
@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

Załóżmy, że Twój model jest zbiorem obiektów (lub może pojedynczym obiektem). Dla każdego obiektu w modelu wykonaj następujące czynności.

1) Umieść obiekt, który chcesz wyświetlić w ViewBag. Na przykład:

  ViewBag.YourObject = yourObject;

2) Dodaj instrukcję using na górze _Layout.cshtml, która zawiera definicję klasy dla twoich obiektów. Na przykład:

@using YourApplication.YourClasses;

3) Kiedy odwołujesz się do obiektu yourObject w _Layout, rzutuj go. Możesz zastosować obsadę dzięki temu, co zrobiłeś w (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; }
}

Użyj IContainsMyModel w swoim układzie.

Rozwiązany. Reguła dotycząca interfejsów.


1
nie wiem, dlaczego zostałeś przegłosowany. Używanie interfejsu, podobnego do tego, co tutaj zrobiłeś, działało w moim kontekście.
costa

-6

Na przykład

@model IList<Model.User>

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

Przeczytaj więcej o nowej dyrektywie @model


Ale co, jeśli chcę przekazać pierwszy element kolekcji do modelu Layout?
SiberianGuy

Musisz pobrać pierwszy element ze swojego kontrolera i ustawić model na @model Model.Użytkownik
Martin Fabik.

Ale chcę, aby moja strona otrzymała IList i Layout - tylko pierwszy element
SiberianGuy

Jeśli dobrze cię zrozumiałem, chcesz, aby model był IList <SomeThing> iw widoku uzyskać pierwszy element kolekcji? Jeśli tak, użyj @ Model.First ()
Martin Fabik

6
Plakat pytał o to, jak przekazać model na stronę _Layout.cshtml, a nie do głównego widoku, który korzysta z układu.
Pure.Krome,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.