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


97

Я вижу свойство строкового макета. Но как я могу явно передать модель в макет?


У меня несколько страниц с разной моделью, но с одинаковым макетом
SiberianGuy

2
Этот вопрос stackoverflow, кажется, отвечает на то, что вы спрашиваете: stackoverflow.com/questions/13225315/…
Пол

У меня нет этой проблемы. Modelдоступен в формате _Layout. Я использую MVC5.
toddmo

Ответы:


66

Похоже, вы смоделировали свои модели просмотра немного неправильно, если у вас есть эта проблема.

Лично я бы никогда не стал печатать макет страницы. Но если вы хотите сделать это, у вас должна быть базовая модель просмотра, от которой наследуются другие ваши модели просмотра, и ввести свой макет в базовую модель просмотра, а страницы - на конкретную.


11
«Лично я бы никогда не стал печатать макет страницы». Зачем? Я имею в виду, как вы обрабатываете побочный динамический контент, который появляется на всех страницах? Вы пропускаете контроллеры из поля зрения? / может быть, вы хотите использовать RenderAction из макета? (Я просто смотрю на это прямо сейчас)
eglasius

52
@eglasius, решение, которое я использую, различается в зависимости от того, о каком контенте мы говорим. Но распространенное решение - использовать RenderAction для визуализации частей, которым нужны их собственные данные на странице макета. Причина, по которой мне не нравится печатать страницу макета, заключается в том, что это заставит вас всегда наследовать «базовую» модель представления во всех ваших конкретных моделях представления. По моему опыту, это обычно не очень хорошая идея, и в большинстве случаев у вас будут проблемы, когда уже слишком поздно менять дизайн (или это займет слишком много времени).
Маттиас Якобссон,

2
Что, если я хочу включить базовую модель путем агрегирования, а не наследования? Совершенно законный способ с точки зрения дизайна. Как мне тогда обрабатывать макет?
Федор Сойкин

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

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 У меня было требование diff, и это действительно помогло мне, спасибо. Можем ли мы добиться того же с помощью TempData, если да, то как и нет, тогда, пожалуйста, скажите мне, почему его нельзя использовать. Еще раз спасибо.
Zaker

2
@User - TempData использует сеанс и всегда кажется мне немного неуклюжим. Насколько я понимаю, он «читается один раз», так что, как только вы его прочтете, он удаляет его из сеанса (или, возможно, сразу после завершения запроса). Возможно, вы храните сеанс на Sql Server (или Dynamo Db), поэтому примите во внимание тот факт, что вам придется сериализовать MasterLayoutViewModel ... скорее всего, не то, что вам нужно. По сути, установка ViewData сохраняет его в памяти в небольшом гибком словаре, который отвечает всем требованиям.
BlackjacketMack

Достаточно просто, я использовал ваше решение, но я новичок в MVC, поэтому мне просто интересно, считается ли это хорошей практикой? или хотя бы неплохой?
Karim AG

1
Привет, Карим 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 06

29

Распространенное решение - создать базовую модель представления, которая содержит свойства, используемые в файле макета, а затем унаследовать от базовой модели модели, используемые на соответствующих страницах.

Проблема с этим подходом заключается в том, что теперь вы заблокировали себя проблемой модели, которая может наследовать только от одного другого класса, и, возможно, ваше решение таково, что вы все равно не можете использовать наследование для модели, которую вы намеревались.

Мое решение также начинается с базовой модели представления:

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
Не могли бы вы подробнее рассказать об этом? Я был бы признателен за ссылку на какой-нибудь пост в блоге,
посвященный

Это может сработать, но зачем снижать производительность? Вы должны дождаться всей обработки, выполненной контроллером, вернуть представление, только чтобы браузер пользователя сделал ДРУГОЙ запрос для получения необходимых данных. А что, если ваш макет зависит от данных, которые нужно правильно отображать. ИМХО это не ответ на этот вопрос.
Бретт Спенсер

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классе создайте метод, который возвращает класс модели, например.

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) Добавьте оператор using вверху _Layout.cshtml, который содержит определение класса для ваших объектов. Например:

@ using 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
не уверен, почему вас проголосовали против. Использование интерфейса, аналогичного тому, что вы сделали здесь, сработало в моем контексте.
costa

-6

Например

@model IList<Model.User>

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

Подробнее о новой директиве @model


Но что, если я хочу передать первый элемент коллекции модели Layout?
SiberianGuy

Вы должны получить первый элемент в вашем контроллере и установить модель на @model Model.User
Мартин Фабик

Но я хочу, чтобы на моей странице были IList и Layout - только первый элемент
SiberianGuy

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

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