ASP.NET MVC Razor meneruskan model ke layout

97

Apa yang saya lihat adalah properti Layout string. Tapi bagaimana saya bisa meneruskan model ke layout secara eksplisit?

SiberianGuy
sumber
Saya memiliki beberapa halaman dengan model yang berbeda tetapi tata letak yang sama
SiberianGuy
2
Pertanyaan stackoverflow ini sepertinya menjawab apa yang Anda tanyakan: stackoverflow.com/questions/13225315/…
Paul
Saya tidak mengalami masalah ini. Modeltersedia di _Layout. Saya menggunakan MVC5.
toddmo

Jawaban:

66

Sepertinya Anda telah membuat model viewmodels Anda sedikit salah jika Anda mengalami masalah ini.

Secara pribadi saya tidak akan pernah mengetik halaman tata letak. Tetapi jika Anda ingin melakukannya, Anda harus memiliki model tampilan dasar yang diwarisi oleh model tampilan lain dan mengetik tata letak Anda ke model tampilan dasar dan halaman Anda ke model tampilan tertentu.

Mattias Jakobsson
sumber
11
"Secara pribadi saya tidak akan pernah mengetik halaman tata letak." Mengapa? Maksud saya, bagaimana Anda menangani konten dinamis samping yang muncul di Semua halaman? Apakah Anda melewatkan pengontrol dari tampilan? / mungkin Anda bermaksud menggunakan RenderAction dari layout? (Saya hanya melihatnya sekarang)
eglasius
52
@eglasius, Solusi yang saya gunakan berbeda bergantung pada jenis konten yang kita bicarakan. Namun solusi umum adalah menggunakan RenderAction untuk merender bagian yang membutuhkan datanya sendiri di halaman tata letak. Alasan saya tidak suka mengetik halaman tata letak adalah karena hal itu akan memaksa Anda untuk selalu mewarisi model tampilan "dasar" di semua model tampilan spesifik Anda. Dalam pengalaman saya, ini biasanya bukan ide yang sangat bagus dan sering kali Anda akan mengalami masalah ketika terlambat untuk mengubah desain (atau akan memakan waktu lama).
Mattias Jakobsson
2
Bagaimana jika saya ingin menyertakan model dasar dengan agregasi, bukan berdasarkan warisan? Cara yang benar-benar sah dari perspektif desain. Lalu bagaimana cara menangani tata letak?
Fyodor Soikin
4
Saya memiliki 2 solusi: model generik untuk tata letak sehingga saya dapat menggunakan MyLayoutModel <MyViewModel> untuk model tampilan, menggunakan RenderP Partial dengan MyViewModel hanya dalam tata letak. Atau render sebagian bagian halaman menggunakan RenderAction untuk bagian cache statis dan panggilan ajax untuk bagian dinamis. Tetapi saya lebih suka solusi pertama karena lebih ramah mesin pencari, dan mudah digabungkan dengan pembaruan ajax.
Softlion
4
Mengerjakan kode lama di mana tepatnya ini telah dilakukan. Ini mimpi buruk. Jangan ketik tata letak Anda ... menyenangkan!
79
  1. Tambahkan properti ke pengontrol Anda (atau pengontrol dasar) yang disebut MainLayoutViewModel (atau apa pun) dengan jenis apa pun yang ingin Anda gunakan.
  2. Dalam konstruktor pengontrol Anda (atau pengontrol dasar), buat instance tipe dan setel ke properti.
  3. Setel ke bidang ViewData (atau ViewBag)
  4. Di halaman Tata Letak, transmisikan properti itu ke tipe Anda.

Contoh: Pengontrol:

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

}

Contoh bagian atas Halaman Tata Letak

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

Sekarang Anda dapat mereferensikan variabel 'viewModel' di halaman tata letak Anda dengan akses penuh ke objek yang diketik.

Saya menyukai pendekatan ini karena kontrolerlah yang mengontrol tata letak, sedangkan model tampilan halaman individual tetap agnostik tata letak.

Catatan untuk MVC Core


Mvc Core tampaknya menghilangkan konten ViewData / ViewBag saat memanggil setiap tindakan untuk pertama kalinya. Artinya, menetapkan ViewData di konstruktor tidak berfungsi. Apa yang berhasil, bagaimanapun, adalah menggunakan IActionFilterdan melakukan pekerjaan yang sama persis di OnActionExecuting. Masukan MyActionFilterpada Anda 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
sumber
1
I get ya ... tapi dinamika / gips cukup sentral untuk halaman pisau cukur. Satu hal yang dapat Anda lakukan adalah menambahkan metode statis ke MainLayoutViewModel yang melakukan casting untuk Anda (misalnya, MainLayoutViewModel.FromViewBag (this.ViewBag)) sehingga setidaknya cast terjadi di satu tempat dan Anda dapat menangani pengecualian di sana dengan lebih baik.
BlackjacketMack
@BlackjacketMack Pendekatan yang baik dan saya mencapainya menggunakan cara di atas dan membuat beberapa modifikasi karena saya memiliki persyaratan yang berbeda dan ini sangat membantu saya, terima kasih. Bisakah kita mencapai hal yang sama menggunakan TempData jika ya lalu bagaimana dan tidak, tolong beri tahu saya mengapa itu tidak dapat digunakan. Terima kasih lagi.
Zaker
2
@User - TempData menggunakan Sesi dan selalu terasa sedikit membingungkan bagi saya. Pemahaman saya adalah bahwa ini 'baca-sekali' sehingga segera setelah Anda membacanya, ia menghapusnya dari sesi (atau mungkin segera setelah permintaan selesai). Ada kemungkinan bahwa Anda menyimpan sesi di Sql Server (atau Dynamo Db) jadi pertimbangkan fakta bahwa Anda harus membuat serial MasterLayoutViewModel ... bukan yang Anda inginkan. Jadi pada dasarnya, menyetelnya ke ViewData akan menyimpannya dalam memori dalam kamus yang sedikit fleksibel, yang sesuai dengan tagihan.
BlackjacketMack
Cukup sederhana, saya menggunakan solusi Anda tetapi, saya baru mengenal MVC jadi, saya hanya ingin tahu apakah ini dianggap praktik yang baik? atau setidaknya tidak buruk?
Karim AG
1
Hai Karim AG, saya rasa ini sedikit dari keduanya. Saya cenderung menganggap menyimpan barang di ViewData sebagai praktik yang buruk (sulit dilacak, berdasarkan kamus, tidak benar-benar diketik) ... TAPI ... mengetik semua properti tata letak Anda dalam objek yang diketik dengan kuat adalah praktik yang bagus. Jadi saya berkompromi dengan mengatakan, oke, mari kita simpan satu hal di sana, tetapi selebihnya dikunci ke ViewModel yang diketik dengan kuat.
BlackjacketMack
30

ini adalah hal yang cukup mendasar, yang perlu Anda lakukan hanyalah membuat model tampilan dasar dan memastikan SEMUA! dan maksud saya SEMUA! dari tampilan Anda yang akan menggunakan tata letak itu akan menerima tampilan yang menggunakan model dasar itu!

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

public class EmptyViewModel : ViewModelBase
{
}

public abstract class ViewModelBase
{
}

di _Layout.cshtml:

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

dalam metode Indeks (misalnya) di pengontrol rumah:

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

Index.cshtml:

@model Models.SomeViewModel

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

<div class="row">

Saya tidak setuju bahwa meneruskan model ke _layout adalah kesalahan, beberapa info pengguna dapat diteruskan dan data dapat diisi dalam rantai pewarisan pengontrol sehingga hanya satu implementasi yang diperlukan.

jelas untuk tujuan yang lebih maju, Anda harus mempertimbangkan untuk membuat kontrol statis kustom menggunakan injeksi dan menyertakan namespace model itu di _Layout.cshtml.

tetapi untuk pengguna dasar ini akan berhasil

Yakir Manor
sumber
Saya setuju denganmu. Terima kasih.
Sebastián Guerrero
1
up dan hanya ingin menyebutkan bahwa ia juga bekerja dengan antarmuka alih-alih kelas dasar
VladL
27

Solusi umum adalah membuat model tampilan dasar yang berisi properti yang digunakan dalam file layout dan kemudian mewarisi dari model dasar ke model yang digunakan di halaman masing-masing.

Masalah dengan pendekatan ini adalah bahwa Anda sekarang telah mengunci diri Anda sendiri ke dalam masalah model yang hanya dapat mewarisi dari satu kelas lain, dan mungkin solusi Anda sedemikian rupa sehingga Anda tidak dapat menggunakan pewarisan pada model yang Anda inginkan.

Solusi saya juga dimulai dengan model tampilan dasar:

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

    public string Title { get;}
}

Apa yang kemudian saya gunakan adalah versi umum dari LayoutModel yang diwarisi dari LayoutModel, seperti ini:

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

    public T PageModel { get; }
}

Dengan solusi ini saya telah memutuskan kebutuhan memiliki warisan antara model tata letak dan model.

Jadi sekarang saya bisa melanjutkan dan menggunakan LayoutModel di Layout.cshtml seperti ini:

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

Dan di halaman Anda dapat menggunakan LayoutModel generik seperti ini:

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

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

Dari pengontrol Anda, Anda cukup mengembalikan model tipe LayoutModel:

public ActionResult Page()
{
    return View(new LayoutModel<Customer>(new Customer() { Name = "Test" }, "Title");
}
Oskar Sjöberg
sumber
1
Bonus untuk menunjukkan berbagai masalah warisan dan bagaimana mengatasinya! Ini adalah jawaban yang lebih baik untuk skalabilitas.
Brett Spencer
1
Solusi terbaik menurut saya. Dari sudut pandang arsitektur, ini dapat diskalakan dan dipelihara. Ini adalah cara yang benar untuk melakukan ini. Saya tidak pernah menyukai ViewBag atau ViewData ..... Keduanya tampak seperti hacky bagi saya.
Jonathan Alfaro
10

Mengapa Anda tidak menambahkan Partial View baru dengan kontroler spesifik milik saya yang meneruskan model yang diperlukan ke tampilan parsial dan akhirnya Render tampilan parsial yang disebutkan di Layout.cshtml Anda menggunakan RenderP Partial atau RenderAction?

Saya menggunakan metode ini untuk menampilkan info pengguna yang masuk seperti nama, gambar profil, dll.

Arya Sh
sumber
2
Bisakah Anda menjelaskan lebih lanjut tentang ini? Saya menghargai tautan ke beberapa entri blog yang menggunakan teknik ini
J86
Ini bisa berhasil tetapi mengapa harus mengambil kinerja yang terpukul? Anda harus menunggu semua pemrosesan dilakukan oleh pengontrol, mengembalikan tampilan, hanya agar browser pengguna membuat permintaan LAIN untuk mendapatkan data yang dibutuhkan. Dan bagaimana jika Layout Anda bergantung pada data untuk dirender dengan tepat. IMHO ini bukan jawaban untuk pertanyaan ini.
Brett Spencer
3

pertanyaan lama tetapi hanya untuk menyebutkan solusi untuk pengembang MVC5, Anda dapat menggunakan Modelproperti yang sama seperti yang terlihat.

The Modelproperti di kedua tampilan dan tata letak assosiated dengan sama ViewDataDictionaryobjek, sehingga Anda tidak harus melakukan kerja ekstra untuk melewati model Anda ke halaman tata letak, dan Anda tidak harus menyatakan @model MyModelNamedalam tata letak.

Tetapi perhatikan bahwa ketika Anda menggunakan @Model.XXXdalam tata letak menu konteks intelliSense tidak akan muncul karena di Modelsini adalah objek dinamis seperti ViewBag.

Laz Ziya
sumber
2

Mungkin secara teknis ini bukan cara yang tepat untuk menanganinya, tetapi solusi paling sederhana dan paling masuk akal bagi saya adalah dengan membuat kelas dan membuat instance-nya dalam tata letak. Ini adalah pengecualian satu kali untuk cara melakukannya yang benar. Jika ini dilakukan lebih dari pada tata letak maka Anda perlu memikirkan kembali secara serius apa yang Anda lakukan dan mungkin membaca beberapa tutorial lagi sebelum melanjutkan lebih jauh dalam proyek Anda.

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

lalu dalam tampilan

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

di .net core Anda bahkan dapat melewati itu dan menggunakan injeksi ketergantungan.

@inject My.Namespace.IMyLayoutModel myLayoutModel

Ini adalah salah satu area yang agak teduh. Tetapi mengingat alternatif yang sangat rumit yang saya lihat di sini, saya pikir itu lebih dari sekadar pengecualian yang baik untuk dibuat atas nama kepraktisan. Terutama jika Anda memastikan untuk membuatnya tetap sederhana dan memastikan logika yang berat (saya berpendapat bahwa sebenarnya tidak boleh ada, tetapi persyaratan berbeda) ada di kelas / lapisan lain di mana tempatnya. Hal ini tentunya lebih baik daripada mencemari SEMUA pengontrol atau model Anda hanya untuk satu tampilan ..

computrius
sumber
2

Ada cara lain untuk mengarsipkannya.

  1. Cukup terapkan kelas BaseController untuk semua pengontrol .

  2. Di BaseControllerkelas, buat metode yang mengembalikan kelas Model seperti misalnya.

public MenuPageModel GetTopMenu() 
{    

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

}
  1. Dan di Layouthalaman Anda dapat memanggil metode ituGetTopMenu()
@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>
Pengembang
sumber
0

Anggaplah model Anda adalah kumpulan objek (atau mungkin satu objek). Untuk setiap objek dalam model, lakukan hal berikut.

1) Letakkan objek yang ingin Anda tampilkan di ViewBag. Sebagai contoh:

  ViewBag.YourObject = yourObject;

2) Tambahkan pernyataan using di bagian atas _Layout.cshtml yang berisi definisi kelas untuk objek Anda. Sebagai contoh:

@ menggunakan YourApplication.YourClasses;

3) Ketika Anda mereferensikan yourObject di _Layout cast itu. Anda dapat menerapkan pemeran karena apa yang Anda lakukan di (2).

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

Gunakan IContainsMyModel dalam tata letak Anda.

Terpecahkan. Aturan antarmuka.

Akan
sumber
1
tidak yakin mengapa Anda mendapat suara negatif. Menggunakan antarmuka, mirip dengan apa yang Anda lakukan di sini, bekerja dalam konteks saya.
costa
-6

Sebagai contoh

@model IList<Model.User>

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

Baca selengkapnya tentang direktif @model baru

Martin Fabik
sumber
Tetapi bagaimana jika saya ingin meneruskan elemen pertama collection ke model Layout?
SiberianGuy
Anda harus mengambil elemen pertama di pengontrol Anda dan menyetel model ke @model Model. Pengguna
Martin
Tapi saya ingin halaman saya mendapatkan IList dan Layout - hanya elemen pertama
SiberianGuy
Jika saya mengerti Anda benar, Anda ingin model menjadi IList <SomeThing> dan dalam tampilan mendapatkan elemen pertama dari koleksi? Jika demikian gunakan @ Model.First ()
Martin Fabik
6
Poster tersebut menanyakan tentang cara meneruskan model ke halaman _Layout.cshtml .. bukan tampilan utama yang menggunakan layout.
Pure.Krome