Zagnieżdżone układy Razor z sekcjami kaskadowymi

80

Mam witrynę MVC3 używającą Razor jako silnika widoku. Chcę, aby moja witryna umożliwiała skórowanie. Większość możliwych skórek jest na tyle podobnych, że mogą pochodzić ze wspólnego układu głównego.

Dlatego rozważam ten projekt:

Schemat widoku planowanego

Chciałbym jednak mieć możliwość wywołania RenderSectiondolnej warstwy _Common.cshtmli renderowania sekcji zdefiniowanej w górnej warstwie Detail.cshtml. To nie działa: RenderSectionnajwyraźniej renderuje tylko sekcje zdefiniowane w następnej warstwie.

Oczywiście mogę zdefiniować każdą sekcję w każdej skórce. Na przykład, jeśli _Commontrzeba wywołać RenderSection("hd")sekcję zdefiniowaną w Detail, po prostu umieszczam to w każdej _Skini działa:

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

Powoduje to pewne zduplikowanie kodu (ponieważ każda skórka musi teraz mieć tę samą sekcję) i generalnie sprawia wrażenie bałaganu. Wciąż jestem nowy w Razor i wygląda na to, że brakuje mi czegoś oczywistego.

Podczas debugowania widzę pełną listę zdefiniowanych sekcji w WebViewPage.SectionWritersStack. Gdybym mógł po prostu powiedzieć RenderSection, aby przejrzał całą listę przed rezygnacją, znalazłby sekcję, której potrzebuję. Niestety, sekcja SectionWritersStack jest niepubliczna.

Alternatywnie, gdybym mógł uzyskać dostęp do hierarchii stron układu i spróbować wykonać RenderSection w każdym innym kontekście, mógłbym zlokalizować potrzebną sekcję. Prawdopodobnie czegoś mi brakuje, ale nie widzę żadnego sposobu, aby to zrobić.

Czy istnieje sposób na osiągnięcie tego celu inny niż metoda, którą już opisałem?

Chris Nielsen
źródło

Odpowiedzi:

35

W rzeczywistości nie jest to obecnie możliwe przy użyciu publicznego interfejsu API (poza zastosowaniem metody redefinicji sekcji). Możesz mieć trochę szczęścia, korzystając z prywatnej refleksji, ale jest to oczywiście delikatne podejście. W następnej wersji Razor zajmiemy się ułatwieniem tego scenariusza.

W międzyczasie kilka postów na blogu, które napisałem na ten temat:

marcind
źródło
3
Dzięki za odpowiedź, bawiłem się zagnieżdżonymi sekcjami przy użyciu tej składni (jak wyżej): @section hd {@RenderSection ("hd")} ... która faktycznie działa dla mnie i wygląda na to, że mogę replikować istniejące zagnieżdżone strony główne . Myślę, że trochę źle zrozumiałem pytanie i pomyślałem, że to nie zadziała.
Mark Redman
2
Zarówno pytanie, jak i odpowiedź bardzo pomogły i również zgadzam się, że w następnej wersji Razora powinno to być ułatwione. Powinieneś także włączyć możliwość częściowych widoków, które mogą również implementować sekcje, co nie jest obecnie obsługiwane.
klacz
1
@Shrike Nie sądzę, żeby coś się zmieniło w tym obszarze. Można wprowadzić zgłoszeń funkcji na stronie UserVoice lub błędy na CodePlex
marcind
1
@marcind Spójrz na moją odpowiedź. Myślę, że o to prosił PO. Dobrze?
Alireza Noori
1
MVC 5 odpadł. Jakakolwiek aktualizacja? Alirzea powiedział, że znalazł rozwiązanie, ale wydaje się, że nie pasuje do problemu PO, ponieważ w ogóle nie odnosi się do sekcji.
Snekse
17
@helper ForwardSection( string section )
{
   if (IsSectionDefined(section))
   {
       DefineSection(section, () => Write(RenderSection(section)));
   }
}

Czy to zadziała?

Krzykliwy
źródło
Czy używasz tego w warstwie pośredniej? Prawie to samo, co ta klasa rozszerzenia ? Jeśli tak, jest to wygodniejsze w przypadku ponownego deklarowania sekcji, a nie rozwiązywania problemu, prawda? Tylko upewniam się, że rozumiem, ponieważ spóźniłem się na tę dyskusję.
drzaus
Dla mnie było to jedyne rozwiązanie, które zadziałało. Jeśli sekcja jest warunkowo renderowana w układzie podstawowym, MVC zgłosi błąd w czasie wykonywania, chyba że ta sekcja jest warunkowo zdefiniowana (w ten sposób) w warstwie pośredniej. Dzięki @Randy!
Michael
Czy można przekazać wszystkie aktualnie zdefiniowane sekcje?
nvirth
4

Nie jestem pewien, czy jest to możliwe w MVC 3, ale w MVC 5 jestem w stanie to pomyślnie zrobić, używając następującej sztuczki:

W ~/Views/Shared/_Common.cshtmlnapisać swój wspólny kod HTML, takich jak:

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

W ~/Views/_ViewStart.cshtml:

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

Teraz wszystko, co musisz zrobić, to użyć _Common.cshtmljako Layoutdla wszystkich skórek. Na przykład w ~/Views/Shared/Skin1.cshtml:

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

<p>Something specific to Skin1</p>

@RenderBody()

Teraz możesz ustawić skórkę jako układ w kontrolerze lub widoku na podstawie swoich kryteriów. Na przykład:

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

Jeśli uruchomisz powyższy kod, powinieneś otrzymać stronę HTML z zawartością Skin1.cshtmli_Common.cshtml

Krótko mówiąc, ustawisz układ strony układu (skóry).

Alireza Noori
źródło
Miałem problemy z tym podejściem, ponieważ sekcje były niewidoczne. Znalazłem rozwiązanie na blogs.msdn.microsoft.com/marcinon/2010/12/15/…
Spikolynn
1

Nie jestem pewien, czy to ci pomoże, ale napisałem kilka metod rozszerzających, aby pomóc w „uwypuklaniu” sekcji z części podrzędnych, co powinno działać również dla układów zagnieżdżonych.

Wstrzykiwanie zawartości do określonych sekcji z częściowego widoku ASP.NET MVC 3 z silnikiem Razor View

Zadeklaruj w układzie potomnym / widoku / częściowym

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

Renderuj w dowolnym rodzicu

@Html.RenderDelayed();

Zobacz link do odpowiedzi, aby uzyskać więcej przypadków użycia, takich jak renderowanie tylko jednego opóźnionego bloku, nawet jeśli został zadeklarowany w widoku powtarzalnym, renderowanie określonych opóźnionych bloków itp.

drzaus
źródło