Вложенные макеты Razor с каскадными разделами

80

У меня есть сайт MVC3, использующий Razor в качестве движка просмотра. Я хочу, чтобы мой сайт можно было изменять. Большинство возможных скинов достаточно похожи, чтобы их можно было извлечь из общего главного макета.

Поэтому рассматриваю такую ​​конструкцию:

Схема планового вида

Тем не менее, я хотел бы иметь возможность вызвать RenderSectionв нижнем слое, _Common.cshtmlи иметь его вынести раздел, определенный в верхнем слое, Detail.cshtml. Это не работает: по- RenderSectionвидимому, визуализируются только те разделы, которые определены на следующем уровне.

Конечно, я могу определить каждую секцию в каждом скине. Например, если _Commonнужно вызвать RenderSection("hd")раздел, определенный в Detail, я просто помещаю его в каждый, _Skinи он работает:

@section hd {
    @RenderSection("hd")
}

Это приводит к некоторому дублированию кода (поскольку каждый скин теперь должен иметь один и тот же раздел) и, как правило, кажется беспорядочным. Я все еще новичок в Razor, и мне кажется, что я упускаю что-то очевидное.

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

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

Есть ли способ достичь этой цели, кроме метода, который я уже описал?

Крис Нильсен
источник

Ответы:

35

Фактически, сегодня это невозможно с использованием общедоступного API (кроме использования подхода переопределения раздела). Возможно, вам повезет с частным отражением, но это, конечно, хрупкий подход. Мы рассмотрим, как упростить этот сценарий в следующей версии Razor.

А пока вот несколько сообщений в блоге, которые я написал на эту тему:

Марсинд
источник
3
Спасибо за ответ, я играл с вложенными разделами, используя этот синтаксис (как указано выше): @section hd {@RenderSection ("hd")} ... который действительно работает для меня и выглядит так, как будто я могу реплицировать существующие вложенные мастер-страницы . Я думаю, что я немного неправильно понял вопрос и подумал, что это не сработает.
Марк Редман,
2
И вопрос, и ответ очень помогли, и я тоже согласен, что это должно быть упрощено в следующей версии Razor. И вы также должны включить возможность частичных представлений, которые также могут реализовывать разделы, что в настоящее время не поддерживается.
mare
1
@Shrike Не думаю, что в этой области что-то изменилось. Вы можете вводить запросы функций на сайте uservoice или ошибки на codeplex
marcind
1
@marcind Взгляните на мой ответ. Я думаю, это то, о чем просил ОП. Правильно?
Алиреза Нури
1
MVC 5 отсутствует. Есть обновления? Алирзеа сказал, что нашел решение, но оно, похоже, не соответствовало проблеме OP, поскольку оно вообще не ссылалось на разделы.
Снексе
17
@helper ForwardSection( string section )
{
   if (IsSectionDefined(section))
   {
       DefineSection(section, () => Write(RenderSection(section)));
   }
}

Сработает ли это?

Рэнди
источник
Вы используете это в промежуточном слое? Практически то же самое, что и этот класс расширения ? Если да, то это скорее удобство при повторном объявлении раздела, а не решение проблемы, верно? Просто убедился, что понимаю, так как я опаздываю на это обсуждение.
drzaus 01
Для меня это было единственное решение, которое сработало. Если раздел условно отображается в базовом макете, MVC выдаст ошибку времени выполнения, если этот раздел не определен условно (как это) на промежуточном уровне. Спасибо @Randy!
Майкл
Можно ли переслать все разделы, которые в настоящее время определены?
nvirth 01
4

Я не уверен, возможно ли это в MVC 3, но в MVC 5 я могу успешно сделать это, используя следующий трюк:

В ~/Views/Shared/_Common.cshtmlзаписи ваш общий HTML - код , как:

<!DOCTYPE html>
<html lang="fa">
<head>
    <title>Skinnable - @ViewBag.Title</title>
</head>
<body>
@RenderBody()
</body>
</html>

В ~/Views/_ViewStart.cshtml:

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

Теперь все, что вам нужно сделать, это использовать в _Common.cshtmlкачестве элемента Layoutдля всех скинов. Например, в ~/Views/Shared/Skin1.cshtml:

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

<p>Something specific to Skin1</p>

@RenderBody()

Теперь вы можете установить скин в качестве макета в контроллере или в представлении в зависимости от ваших критериев. Например:

    public ActionResult Index()
    {
        //....
        if (user.SelectedSkin == Skins.Skin1)
            return View("ViewName", "Skin1", model);
    }

Если вы запустите приведенный выше код, вы должны получить HTML-страницу с содержимым Skin1.cshtmlи_Common.cshtml

Короче говоря, вы установите макет для страницы макета (скина).

Алиреза Нури
источник
У меня были проблемы с этим подходом, потому что разделы не были видны. Я нашел решение на blogs.msdn.microsoft.com/marcinon/2010/12/15/…
Spikolynn
1

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

Внедрение содержимого в определенные разделы из частичного представления ASP.NET MVC 3 с Razor View Engine

Объявить в дочернем макете / представлении / частичном

@using (Html.Delayed()) {
    <b>show me multiple times, @Model.Whatever</b>
}

Рендеринг в любом родителе

@Html.RenderDelayed();

См. Ссылку для ответа на другие варианты использования, например, рендеринг только одного отложенного блока, даже если он объявлен в повторяющемся представлении, рендеринг определенных отложенных блоков и т. Д.

Drzaus
источник