Nested layout pages in Razor
This post is for people using the Razor view engine in ASP.NET MVC3.
Preamble
In any reasonably sized website you will want to use layout pages for the common header and footer parts of HTML. Razor makes this easy and the syntax is elegant (especially when compared to the old webforms view engine). However, on more complex websites there is often a need to nest layouts.
Consider a web application that has some common scripts and styles across all pages, but a number of "areas" that have their own HTML structure. For example, a "public" area, an "admin" area and a "customer" area. We'd like to define a layout for the top-level HTML, stylesheets and scripts. Then define layouts for each area and have them inherit from the top-level layout.
Nested Layouts
The top-level layout looks like a regular Razor layout. Let's create a file:
~/Views/Shared/_site.cshtml
<!DOCTYPE html>
<html>
<head>
<title>@View.Title</title>
</head>
<body>
@RenderBody()
<p>Copyright Me 2010</p>
<script type="text/javascript" src="/scripts/jquery.js"></script>
@RenderSection("scripts", required: false)
</body>
</html>
Note that this top-level layout defines where it's inner content will go (RenderBody) and also has an optional "scripts" section. Pages using this layout will be able to specify the extra scripts they need in that section.
For each area, we can now create a nested layout page. For example, in the "Public"
area we'll create a file:
~/Areas/Public/Views/Shared/_public.cshtml
@{ Layout = "~/Views/Shared/_site.cshtml"; }
<div id="public">
<p>public header</p>
@RenderBody()
<p>public footer</p>
</div>
@section scripts {
<script type="text/javascript" src="/scripts/public.js"><script>
@RenderSection("scripts", required: false)
}
This nested layout inherits from the top-level layout. It adds some HTML common to all pages in the public area and defines where the page content will go (RenderBody).
It also has to redefine the scripts section. This has to be there even when not actually adding any HTML to the section, otherwise content pages will not be able to add to the section. This is just like nested master pages in webforms using ContentPlaceholders.
A view in the public area will look like this:
@{
Layout = "~/Areas/Public/Views/Shared/_public.cshtml";
View.Title = "Home Page";
}
<p>Welcome to my public website!</p>
End Result
When the home page view is rendered the top-level and area-level layouts are nested correctly. The output will look like this:
<!DOCTYPE html>
<html>
<head>
<title>Home Page</title>
</head>
<body>
<div id="public">
<p>public header</p>
<p>Welcome to my public website!</p>
<p>public footer</p>
</div>
<p>Copyright Me 2010</p>
<script type="text/javascript" src="/scripts/jquery.js"></script>
<script type="text/javascript" src="/scripts/public.js"><script>
</body>
</html>