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

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


person SiberianGuy    schedule 11.11.2010    source источник
comment
У меня есть несколько страниц с другой моделью, но с одинаковым макетом   -  person SiberianGuy    schedule 11.11.2010
comment
Этот вопрос stackoverflow, кажется, отвечает на то, что вы спрашиваете: stackoverflow.com/questions/13225315/   -  person Paul    schedule 28.07.2014
comment
У меня нет этой проблемы. Model доступен в _Layout. Я использую MVC5.   -  person toddmo    schedule 28.08.2016


Ответы (12)


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

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

person Mattias Jakobsson    schedule 11.11.2010
comment
Лично я бы никогда не печатал страницу макета. Почему? Я имею в виду, как вы обрабатываете побочный динамический контент, который появляется на всех страницах? Вы пропускаете контроллеры из представления? / может быть, вы имеете в виду использовать RenderAction из макета? (только сейчас смотрю) - person eglasius; 12.04.2011
comment
@eglaius, решение, которое я использую, зависит от того, о каком контенте мы говорим. Но распространенным решением является использование RenderAction для рендеринга частей, которым нужны собственные данные на странице макета. Причина, по которой мне не нравится вводить страницу макета, заключается в том, что это заставит вас всегда наследовать базовую модель представления во всех ваших конкретных моделях представления. По моему опыту, это обычно не очень хорошая идея, и в большинстве случаев у вас будут проблемы, когда уже поздно менять дизайн (или это займет много времени). - person Mattias Jakobsson; 12.04.2011
comment
спасибо за ответ, уже решил для RenderAction через несколько минут после того, как я опубликовал. То, что вы говорите в комментарии, очень правильно, я просто не видел, где зацепить в то время. - person eglasius; 12.04.2011
comment
Что, если я хочу включить базовую модель путем агрегирования, а не по наследству? Совершенно законный способ с точки зрения дизайна. Как мне обрабатывать макет тогда? - person Fyodor Soikin; 22.08.2011
comment
-1, потому что я также думаю, что макет должен иметь модель, которую можно установить в представлении или с помощью действия, отображающего представление. В ViewBag слишком много волшебства, и он помнит мое магическое объявление переменных в базовом/javascript, что приводит к часам отладки. - person Softlion; 05.01.2012
comment
@Softlion Итак, вы строго печатаете свой макет и наследуете ли модели представления контента от этого типа? - person Ian Warburton; 03.03.2012
comment
У меня есть 2 решения: общая модель для макета, поэтому я могу использовать MyLayoutModel‹MyViewModel› для модели представления, используя RenderPartial с MyViewModel только в макете. Или частично визуализируйте части страницы, используя RenderAction для статических кэшированных частей и вызовы ajax для динамических частей. Но я предпочитаю первое решение, поскольку оно более удобно для поисковых систем и легко сочетается с обновлениями ajax. - person Softlion; 05.03.2012
comment
Работа над устаревшим кодом, где именно это и было сделано. Это кошмар. Не печатайте свои макеты...пожалуйста! - person ; 19.09.2013
comment
Конструктор базового контроллера. Просмотр данных - person williamsandonz; 08.01.2014
comment
Есть только одна проблема с RenderAction/Action на странице макета. Экземпляр контроллера создается для каждого такого вызова. Это может быть еще более серьезной проблемой, если дизайн изменяется, и базовый контроллер должен выполнять больше работы в конструкторе/перед действием. Вот почему мой голос за @BlackjacketMack, хотя я ненавижу использовать ViewBag (но все же лучше, чем базовая модель). - person Stefan Cebulak; 09.08.2014
comment
Итак, можете ли вы предоставить соответствующий пример кода RenderAction? - person niico; 06.03.2019
comment
@MattiasJakobsson, у меня есть типизированный внутренний вложенный макет, чтобы обеспечить общий макет для небольшого подмножества общих страниц; элементы заголовка, вступления, нижнего колонтитула. Если страница должна отличаться от этого, я бы отказался от внутреннего макета и включил эти элементы в представление. Предвидите ли вы какие-либо проблемы, с которыми я могу столкнуться? - person Myster; 01.06.2021

  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;
            }
        }
    }
person BlackjacketMack    schedule 14.02.2013
comment
Я понимаю ... но динамика / актеры довольно важны для бритвенных страниц. Одна вещь, которую вы можете сделать, это добавить статический метод в MainLayoutViewModel, который выполняет приведение за вас (например, MainLayoutViewModel.FromViewBag(this.ViewBag)), поэтому, по крайней мере, приведение происходит в одном месте, и вы можете лучше обрабатывать исключения там. - person BlackjacketMack; 27.02.2014
comment
@BlackjacketMack Хороший подход, и я добился этого, используя вышеизложенное и внеся некоторые изменения, потому что у меня были требования к различиям, и это действительно помогло мне, спасибо. Можем ли мы добиться того же, используя TempData, если да, то как, а нет, то, пожалуйста, скажите мне, почему его нельзя использовать. Еще раз спасибо. - person Zaker; 21.01.2015
comment
@User - TempData использует сеанс и всегда кажется мне немного неуклюжим. Насколько я понимаю, это «однократное чтение», поэтому, как только вы его прочитаете, оно удаляется из сеанса (или, возможно, как только запрос завершен). Возможно, вы храните сеанс на сервере Sql (или Dynamo Db), поэтому учтите тот факт, что вам придется сериализовать MasterLayoutViewModel... скорее всего, не то, что вы хотите. Таким образом, в основном, установка его в ViewData сохраняет его в памяти в небольшом гибком словаре, который отвечает всем требованиям. - person BlackjacketMack; 03.03.2015
comment
Достаточно просто, я использовал ваше решение, но я новичок в MVC, поэтому мне просто интересно, считается ли это хорошей практикой? или хотя бы не плохой? - person Karim AG; 28.10.2015
comment
Привет Карим АГ, я думаю, что это и то, и другое. Я склонен считать хранение материала в ViewData плохой практикой (это трудно отследить, основано на словаре, на самом деле не типизировано)... НО... ввод всех ваших свойств макета в строго типизированный объект - отличная практика. Поэтому я иду на компромисс, говоря: «Хорошо, давайте сохраним одну вещь там, но остальную часть заблокируем в хорошей строго типизированной ViewModel». - person BlackjacketMack; 28.10.2015
comment
Есть ли у вас информация о том, что MVC Core сбрасывает ViewData/ViewBag при первом вызове каждого действия? Возможно, сейчас это исправили? - person niico; 06.03.2019
comment
В нижней части моего ответа выше у меня есть раздел «Примечания для MVC Core», который должен решить эту проблему. - person BlackjacketMack; 14.03.2019
comment
@BlackjacketMack Я попробовал ваше решение, но мне нужно передать идентификатор из URL-адреса, чтобы я мог прочитать правильную информацию из базы данных, чтобы построить модель представления для макета. Является ли это возможным? - person Patrick; 22.01.2020
comment
@Patrick ActionExecutingContext (переменная «контекст») имеет доступ к HttpContext, поэтому я подозреваю, что вы можете получить идентификатор из URL-адреса либо через RequestContext (в HttpContext), либо, возможно, через RouteValues, если ваши маршруты настроены таким образом. - person BlackjacketMack; 23.01.2020
comment
Спасибо, это сработало. - person Arun Prasad E S; 17.01.2021

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

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

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

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");
}
person Oskar Sjöberg    schedule 19.12.2017
comment
Бонус за указание на проблему множественного наследования и способы ее решения! Это лучший ответ для масштабируемости. - person Brett Spencer; 02.05.2018
comment
Лучшее решение на мой взгляд. С архитектурной точки зрения он масштабируемый и ремонтопригодный. Это правильный способ сделать это. Мне никогда не нравились ViewBag или ViewData..... Они оба кажутся мне хакерскими. - person Jonathan Alfaro; 10.02.2019

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

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

Индекс.cshtml:

@model Models.SomeViewModel

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

<div class="row">

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

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

но для обычных пользователей это поможет

person Yakir Manor    schedule 31.03.2015
comment
Я согласен с тобой. Спасибо. - person Sebastián Guerrero; 05.01.2016
comment
up и просто хочу отметить, что он также работает с интерфейсом вместо базового класса - person VladL; 06.02.2018

Почему бы вам просто не добавить новый частичный вид с собственным конкретным контроллером i, передающим требуемую модель частичному представлению, и, наконец, визуализировать упомянутый частичный вид в вашем Layout.cshtml с помощью RenderPartial или RenderAction ?

Я использую этот метод для отображения информации о вошедшем в систему пользователе, такой как имя, изображение профиля и т. Д.

person Arya Sh    schedule 21.02.2015
comment
Можете ли вы уточнить это, пожалуйста? Я был бы признателен за ссылку на какой-нибудь пост в блоге, который проходит через эту технику. - person J86; 21.02.2017
comment
Это может сработать, но зачем терять производительность? Вам нужно дождаться всей обработки, выполненной контроллером, вернуть представление только для того, чтобы браузер пользователя сделал ДРУГОЙ запрос для получения необходимых данных. И что, если ваш макет зависит от данных для надлежащего отображения. ИМХО это не ответ на этот вопрос. - person Brett Spencer; 02.05.2018

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

Свойство Model как в представлении, так и в макете связано с одним и тем же объектом ViewDataDictionary, поэтому вам не нужно выполнять дополнительную работу, чтобы передать вашу модель на страницу макета, и вам не нужно объявлять @model MyModelName в макете.

Но обратите внимание, что когда вы используете @Model.XXX в макете, контекстное меню IntelliSense не отображается, потому что Model здесь является динамическим объектом, как и ViewBag.

person LazZiya    schedule 17.10.2017

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

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

тогда в представлении

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

в .net core вы даже можете пропустить это и использовать внедрение зависимостей.

@inject My.Namespace.IMyLayoutModel myLayoutModel

Это одна из тех областей, которая является своего рода теневой. Но, учитывая чрезвычайно сложные альтернативы, которые я вижу здесь, я думаю, что это более чем нормальное исключение, сделанное во имя практичности. Особенно, если вы позаботитесь о том, чтобы все было просто, и убедитесь, что любая сложная логика (я бы сказал, что ее действительно не должно быть, но требования различаются) находится в другом классе/слое, которому она принадлежит. Это, безусловно, лучше, чем загрязнять ВСЕ ваши контроллеры или модели ради одного представления.

person computrius    schedule 29.06.2017

Есть еще один способ заархивировать.

  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>
person DmitryBoyko    schedule 06.02.2020

Предположим, что ваша модель представляет собой набор объектов (или, может быть, один объект). Для каждого объекта в модели выполните следующие действия.

1) Поместите объект, который вы хотите отобразить, в ViewBag. Например:

  ViewBag.YourObject = yourObject;

2) Добавьте оператор using вверху файла _Layout.cshtml, который содержит определение класса для ваших объектов. Например:

@using YourApplication.YourClasses;

3) Когда вы ссылаетесь на свой объект в _Layout, приведите его. Вы можете применить приведение из-за того, что вы сделали в (2).

person HappyPawn8    schedule 17.10.2016

Только сверху, но простая реализация

Индексная страница

@model CMS.Models.IndexViewModel 

@{
    ViewBag.PageModel = Model;    
}

Макет страницы

@{
    var Model = (CMS.Models.IndexViewModel)ViewBag.PageModel;        
}
person Arun Prasad E S    schedule 17.01.2021

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 в макете.

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

person Will    schedule 06.02.2015
comment
не знаю, почему за вас проголосовали. Использование интерфейса, похожего на то, что вы сделали здесь, сработало в моем контексте. - person costa; 01.05.2018

Например

@model IList<Model.User>

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

Узнайте больше о новом @model директива

person Martin Fabik    schedule 11.11.2010
comment
Но что, если я хочу передать первый элемент коллекции в макетную модель? - person SiberianGuy; 11.11.2010
comment
Вы должны получить первый элемент в своем контроллере и установить модель в @model Model.User - person Martin Fabik; 11.11.2010
comment
Но я хочу, чтобы моя страница получала IList и Layout — только первый элемент - person SiberianGuy; 11.11.2010
comment
Если я вас правильно понял, вы хотите, чтобы модель была IList‹SomeThing› и в представлении получался первый элемент коллекции? Если это так, используйте @Model.First() - person Martin Fabik; 11.11.2010
comment
Разве вы не можете установить View.FirstItem = model.First() в своем блоке кода бритвы, а затем сослаться на него в макете представления @View.FirstItem? - person mikekidder; 15.11.2010
comment
Плакат спрашивал о том, как передать модель на страницу _Layout.cshtml, а не на основное представление, которое использует макет. - person Pure.Krome; 16.10.2011