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

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

Поэтому я рассматриваю этот дизайн:

Planned view diagram

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

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

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

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

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

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

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

Ответ 1

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

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

Ответ 2

@helper ForwardSection( string section )
{
   if (IsSectionDefined(section))
   {
       DefineSection(section, () => Write(RenderSection(section)));
   }
}

Будет ли это выполняться?

Ответ 3

Я не уверен, что это возможно в 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

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

Ответ 4

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

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

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

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

Render в любом родителе

@Html.RenderDelayed();

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