Razor Nested Layouts with Cascading Sections
Asked Answered
E

4

81

I have an MVC3 site using Razor as its view engine. I want my site to be skinnable. Most of the possible skins are similar enough that they can derive from a shared master layout.

Therefore, I am considering this design:

Planned view diagram

However, I would like to be able to call RenderSection in the bottom layer, _Common.cshtml, and have it render a section that is defined in the top layer, Detail.cshtml. This doesn't work: RenderSection apparently only renders sections that are defined the next layer up.

Of course, I can define each section in each skin. For instance, if _Common needs to call RenderSection("hd") for a section defined in Detail, I just place this in each _Skin and it works:

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

This results in some duplication of code (since each skin must now have this same section) and generally feels messy. I'm still new to Razor, and it seems like I might be missing something obvious.

When debugging, I can see the complete list of defined sections in WebViewPage.SectionWritersStack. If I could just tell RenderSection to look through the entire list before giving up, it would find the section I need. Alas, SectionWritersStack is non-public.

Alternatively, if I could access the hierarchy of layout pages and attempt execution of RenderSection in each different context, I could locate the section I need. I'm probably missing something, but I don't see any way to do this.

Is there some way to accomplish this goal, other than the method I've already outlined?

Esbenshade answered 2/4, 2011 at 20:16 Comment(0)
K
36

This is in fact not possible today using the public API (other than using the section redefinition approach). You might have some luck using private reflection but that of course is a fragile approach. We will look into making this scenario easier in the next version of Razor.

In the meantime, here's a couple of blog posts I've written on the subject:

Kortneykoruna answered 3/4, 2011 at 4:48 Comment(10)
Thank you for your answer! I acheived partial success by extended WebViewPage with my own type, and adding some code in the constructor to register each new ViewPage as it was created. I could then search my list to find a ViewPage that reported "true" for IsSectionDefined("something"). However, some errors still remained, and I (frustrated) deemed the approach too complex to be worthwhile. Redefining sections seems simpler and easier.Esbenshade
@Mark no updates at this time. The next release of Razor is still quite a while away.Kortneykoruna
Thanks for the response, I have been playing with the nested Sections using this syntax (as above): @section hd { @RenderSection("hd") } ... which actually works for me and looks like I can replicate existing Nested MasterPages. I think I misunderstood the question slightly and thought this wouldnt work.Masterstroke
Both the question and the answer helped a lot and I too agree that this should be made easier in next version of Razor. And you should also enable the possibility of partial views being able to implement sections too, which is not supported at the moment.Cuttle
@marcind, is there any news about solving the issue in MVC 4? Can we use sections from parent layout?Skean
@Skean I don't think anything got changed in this area. You can enter feature requests on the uservoice site or bugs on codeplexKortneykoruna
@Kortneykoruna Take a look at my answer. I think this is what the OP asked for. Right?Playgoer
MVC 5 is out. Any update? Alirzea said he found a solution, but it didn't seem to match the OPs problem since it didn't reference sections at all.Aerification
@Kortneykoruna - I need a little advice from a GURU like you. I have a question here: https://mcmap.net/q/260625/-asp-net-mvc-razor-sections-and-partials/1735836 Thank you in advance for your help.Exigent
@Kortneykoruna your links are dead :(Stearne
L
17
@helper ForwardSection( string section )
{
   if (IsSectionDefined(section))
   {
       DefineSection(section, () => Write(RenderSection(section)));
   }
}

Would this do the job ?

Lighthouse answered 15/10, 2011 at 21:37 Comment(3)
Are you using this in the intermediate layer? Pretty much the same as this extension class? If so, this is more of a convenience when redeclaring a section, rather than solving the problem, right? Just making sure I understand, since I'm late to this discussion.Golding
For me, this was the only solution that worked. If a section is conditionally rendered in the base layout, MVC will throw a runtime error unless that section is conditionally defined (like this) in the intermediate layer. Thanks @Randy!Busty
Is it possible to forward all the sections which are currently defined?Syllable
P
4

I'm not sure if this is possible in MVC 3 but in MVC 5 I am able to successfully do this using the following trick:

In ~/Views/Shared/_Common.cshtml write your common HTML code like:

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

In ~/Views/_ViewStart.cshtml:

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

Now all you have to do is to use the _Common.cshtml as the Layout for all the skins. For instance, in ~/Views/Shared/Skin1.cshtml:

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

<p>Something specific to Skin1</p>

@RenderBody()

Now you can set the skin as your layout in controller or view based on your criteria. For example:

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

If you run the code above you should get a HTML page with both the content of Skin1.cshtml and _Common.cshtml

In short, you'll set the layout for the (skin) layout page.

Playgoer answered 29/12, 2013 at 15:13 Comment(2)
I had problems with this approach because sections would not be visible. I found the solution at blogs.msdn.microsoft.com/marcinon/2010/12/15/…Weatherboard
This isnt the problem he (or I) are facing. The problem is Detail.cshtml defines a Section that is not defined in Skin1-3 but IS defined in _Common.cshtml. The section doesnt automatically pass through and you have to redfine each section in each middle Layout page.Collaborate
G
1

Not sure if this will help you, but I wrote some extension methods to help "bubble up" sections from within partials, which should work for nested layouts as well.

Injecting content into specific sections from a partial view ASP.NET MVC 3 with Razor View Engine

Declare in child layout/view/partial

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

Render in any parent

@Html.RenderDelayed();

See the answer link for more use-cases, like only rendering one delayed block even if declared in a repeating view, rendering specific delayed blocks, etc.

Golding answered 9/12, 2014 at 20:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.