ASP.NET MVC Бритви передають модель в макет


97

Що я бачу, це властивість Layout string. Але як я можу явно передати модель на макет?


У мене є кілька сторінок з іншою моделлю, але однаковий макет
SiberianGuy

2
Це запитання про stackoverflow, здається, відповідає на те, що ви запитуєте: stackoverflow.com/questions/13225315/…
Павло

У мене немає цієї проблеми. Modelдоступно в _Layout. Я використовую MVC5.
tddmo

Відповіді:


66

Схоже, ви моделювали свої моделі перегляду трохи неправильно, якщо у вас є ця проблема.

Особисто я ніколи б не вводив сторінку макета. Але якщо ви хочете зробити це, у вас має бути базовий перегляд моделей, від якого переймаються ваші інші моделі перегляду, і введіть ваш макет до базової моделі перегляду, а ви один раз перейдете на конкретні сторінки.


11
"Особисто я ніколи б не вводив сторінку макета." Чому? Я маю на увазі, як ви обробляєте бічний динамічний контент, який з’являється на Усі сторінки? Ви пропускаєте контролери з виду? / можливо ви маєте на увазі використовувати RenderAction від макета? (Я просто зараз на це
дивлюсь

52
@eglasius, рішення, яке я використовую, відрізняється залежно від того, про який контент ми говоримо. Але загальним рішенням є використання RenderAction для візуалізації частин, які потребують власних даних на сторінці макета. Причина, що мені не подобається вводити сторінку макета, полягає в тому, що вона змусить вас успадковувати "базовий" режим перегляду у всіх конкретних моделях перегляду. На мій досвід, це, як правило, не дуже гарна ідея, і багато часу у вас виникнуть проблеми, коли пізно змінити дизайн (або це займе багато часу).
Маттіас Якобссон

2
Що робити, якщо я хочу включити базову модель за агрегацією, а не за спадщиною? Цілком законний спосіб з точки зору дизайну. Як потім обробляти макет?
Федір Сойкін

4
У мене є 2 рішення: загальна модель для макета, тому я можу використовувати MyLayoutModel <MyViewModel> для моделі перегляду, використовуючи RenderPartial з MyViewModel лише в макеті. Або частково візуалізуйте частини сторінки, використовуючи RenderAction для статичних кешованих частин та ajax-виклики для динамічних частин. Але я вважаю за краще перше рішення, оскільки воно більш дружнє до пошукових систем, і його можна легко поєднувати з оновленнями ajax.
Softlion

4
Робота над застарілим кодом, де саме це було зроблено. Це кошмар. Не вводьте свої макети ... благай!

79
  1. Додайте властивість до свого контролера (або базового контролера) під назвою MainLayoutViewModel (або будь-якого іншого) з будь-яким типом, який ви хочете використовувати.
  2. У конструкторі вашого контролера (або базового контролера) інстанціюйте тип та встановіть його у властивість.
  3. Встановіть його в поле ViewData (або ViewBag)
  4. На сторінці "Макет" передайте це властивість своєму типу.

Приклад: Контролер:

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

}

Приклад вгорі сторінки "Макет"

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

Тепер ви можете посилатися на змінну 'viewModel' на своїй сторінці макета з повним доступом до введеного об’єкта.

Мені подобається такий підхід, тому що саме контролер керує компонуванням, в той час як окремі моделі перегляду сторінок залишаються агностичними.

Примітки для MVC Core


Здається, що Mvc Core видуває вміст ViewData / ViewBag при першому виклику кожної дії. Це означає, що призначення ViewData в конструкторі не працює. Однак, що працює, це використання IActionFilterта виконання точно такої самої роботи OnActionExecuting. Одягніть MyActionFilterсвоє 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
Я розумію, але динаміка / касти є досить важливими для сторінок бритви. Одне, що ви можете зробити, - це додати статичний метод до MainLayoutViewModel, який робить кастинг для вас (наприклад, MainLayoutViewModel.FromViewBag (this.ViewBag)), так що принаймні кастинг відбувається в одному місці, і ви можете краще обробляти винятки там.
BlackjacketMack

@BlackjacketMack Хороший підхід, і я досяг цього за допомогою вищезазначеного та внісши деяку модифікацію bcoz, у мене виникла вимога, і це мені дуже допомогло. Чи можемо ми досягти того ж, використовуючи TempData, якщо так, то як і ні тоді, скажіть мені, чому це не можна використовувати. Знову дякую.
Закер

2
@User - TempData використовує Session і завжди відчуває мене трохи неприємно. Я розумію, що це "один раз прочитаний", так що як тільки ви його прочитаєте, вилучаєте його з сеансу (або, можливо, як тільки запит закінчується). Можливо, що ви зберігаєте сеанс на сервері Sql (або Dynamo Db), тому враховуйте той факт, що вам доведеться серіалізувати MasterLayoutViewModel ... не те, що ви хочете, швидше за все. Отже, встановивши його на ViewData, він зберігає його в пам'яті в невеликому гнучкому словнику, який відповідає рахунку.
BlackjacketMack

Досить просто, я використав ваше рішення, але я новачок у MVC, тому мені просто цікаво, чи вважається це гарною практикою? чи принаймні не поганий?
Карим АГ

1
Привіт Karim AG, я думаю, що це трохи обох. Я схильний вважати зберігання речей у ViewData поганою практикою (важко відстежувати, на основі словника, насправді не набирається) ... АЛЕ ... введення всіх властивостей макета у сильно набраному об’єкті - чудова практика. Тож я йду на компроміс, кажучи: добре, давайте збережемо одне в ньому, але залиште його зафіксованим до хорошого сильно набраного ViewModel.
BlackjacketMack

30

це досить основні речі, все, що вам потрібно зробити, це створити модель базового виду і переконатися ВСІМ! і я маю на увазі ВСЕ! ваших переглядів, які коли-небудь використовуватимуть цей макет, отримають представлення, які використовують цю базову модель!

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

public class EmptyViewModel : ViewModelBase
{
}

public abstract class ViewModelBase
{
}

у _Layout.cshtml:

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

у методі Index (наприклад) в домашньому контролері:

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

Я не погоджуюся, що передача моделі до _layout є помилкою, деяка інформація про користувача може бути передана, і дані можуть бути заповнені у ланцюжку успадкування контролерів, тому потрібна лише одна реалізація.

Очевидно, для більш досконалої мети слід розглянути можливість створення спеціального статичного контакту за допомогою ін'єкції та включити цей простір імен моделі в _Layout.cshtml.

але для основних користувачів це зробить трюк


Я погоджуюсь з тобою. Дякую.
Себастьян Герреро

1
і просто хочу зазначити, що він також працює з інтерфейсом замість базового класу
VladL

27

Загальним рішенням є створення базової моделі подання, яка містить властивості, що використовуються у файлі компонування, а потім успадковується від базової моделі до моделей, що використовуються на відповідних сторінках.

Проблема такого підходу полягає в тому, що тепер ви замкнулися в проблемі, що модель може успадковувати лише один клас, і, можливо, ваше рішення таке, що ви не можете використовувати спадщину на тій моделі, яку ви задумали.

Моє рішення також починається з базової моделі перегляду:

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

    public string Title { get;}
}

Тоді я використовую загальну версію LayoutModel, яка успадковується від LayoutModel, як це:

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

    public T PageModel { get; }
}

Цим рішенням я відключив необхідність спадкування між моделлю компонування та моделлю.

Тож тепер я можу йти вперед і використовувати LayoutModel в Layout.cshtml так:

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

А на сторінці ви можете використовувати загальний LayoutModel таким чином:

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

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

З контролера ви просто повертаєте модель типу LayoutModel:

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

1
Бонус за вказівку на питання про множинні спадщини та як з цим боротися! Це краща відповідь за масштабованість.
Бретт Спенсер

1
Найкраще рішення на мій погляд. З архітектурної точки зору це масштабованість і підтримка. Це правильний спосіб зробити це. Мені ніколи не подобалося ViewBag або ViewData ..... Вони обоє здаються мені хиткими.
Джонатан Альфаро

10

Чому ви просто не додаєте новий частковий вигляд із власним конкретним контролером i, який передає необхідну модель частковому виду, і, нарешті, надайте згаданий частковий вигляд на Layout.cshtml за допомогою RenderPartial або RenderAction?

Я використовую цей метод для відображення інформації про користувача, що увійшли, наприклад, ім'я, зображення профілю та ін.


2
Чи можете ви детальніше зупинитися на цьому? Я буду вдячний за посилання на якусь публікацію в блозі, яка проходить через цю техніку
J86,

Це може спрацювати, але навіщо брати участь у виконанні? Вам потрібно дочекатися, коли вся обробка, виконана контролером, повернути перегляд, лише щоб браузер користувача зробив ВСІМ запит, щоб отримати необхідні дані. А що робити, якщо ваш макет залежить від належних даних для відображення. ІМХО це не відповідь на це питання.
Бретт Спенсер

3

старе питання, але просто згадати про рішення для розробників MVC5, ви можете використовувати Modelвластивість так само, як і у перегляді.

ModelВластивість як в цілях і компоновках , асоційований з тим же ViewDataDictionaryоб'єктом, так що вам не потрібно робити якусь - яку додаткову роботу , щоб передати свій телевізор на сторінку макета, і ви не повинні оголосити @model MyModelNameв макеті.

Але зауважте, що при використанні @Model.XXXв макеті контекстне меню intelliSense не з’явиться, оскільки Modelтут динамічний об’єкт так само, як і він ViewBag.


2

Можливо, це технічно не правильний спосіб поводження з ним, але найпростішим і розумним рішенням для мене є просто скласти клас та інстанціювати його у макеті. Це одноразовий виняток з інакше правильного способу зробити це. Якщо це зроблено більше, ніж у макеті, то вам потрібно серйозно переосмислити, що ви робите, і, можливо, прочитати ще кілька навчальних посібників, перш ніж прогресувати далі у своєму проекті.

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

то в огляді

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

в ядрі .net можна навіть пропустити це та використовувати ін'єкцію залежності.

@inject My.Namespace.IMyLayoutModel myLayoutModel

Це одна з тих областей, яка є тіньовою. Але з огляду на надзвичайно складні альтернативи, які я бачу тут, я вважаю, що це більше, ніж нормально, виняток зробити в ім'я практичності. Особливо, якщо ви переконайтеся, що це просто і переконайтеся, що будь-яка важка логіка (я б стверджував, що насправді не повинно бути жодної, але вимоги відрізняються) знаходиться в іншому класі / шарі, де вона належить. Це, безумовно, краще, ніж забруднювати ВСІ ваші контролери чи моделі заради одного лише представлення.


2

Є ще один спосіб його архівувати.

  1. Просто реалізуйте клас BaseController для всіх контролерів .

  2. У BaseControllerкласі створіть метод, який повертає клас Model, наприклад,

public MenuPageModel GetTopMenu() 
{    

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

}
  1. І на Layoutсторінці ви можете викликати цей метод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

Припустимо, що ваша модель - це сукупність об'єктів (або, можливо, одного об’єкта). Для кожного об'єкта в моделі виконайте наступне.

1) Помістіть об’єкт, який потрібно відобразити, у ViewBag. Наприклад:

  ViewBag.YourObject = yourObject;

2) Додайте використовувальний оператор у верхній частині _Layout.cshtml, який містить визначення класу для ваших об'єктів. Наприклад:

@ використовуючи YourApplication.YourClasses;

3) Коли ви посилаєтесь на вашОб'єкт у _Layout, відкиньте його. Ви можете застосувати акторський склад завдяки тому, що ви зробили в (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; }
}

Використовуйте IContainsMyModel у своєму макеті.

Вирішено. Правило інтерфейсів.


1
не впевнений, чому ви були голосовані. Використання інтерфейсу, аналогічного тому, що ви робили тут, працював у моєму контексті.
Коста

-6

Наприклад

@model IList<Model.User>

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

Детальніше про нову директиву @model


Але що робити, якщо я хочу передати перший елемент колекції моделі Layout?
SiberianGuy

Ви повинні отримати перший елемент у своєму контролері та встановити модель на @model Model.User
Martin Fabik

Але я хочу, щоб моя сторінка отримала IList та Layout - лише перший елемент
SiberianGuy

Якщо я правильно вас зрозумів, ви хочете, щоб модель була IList <SomeThing> і на виду отримували перший елемент колекції? Якщо так, використовуйте @ Model.First ()
Мартін Фабік

6
Плакат запитував про те, як передати модель на сторінку _Layout.cshtml .. не головний вигляд, який використовує макет.
Pure.Krome
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.