ASP.NET MVC 3 Layouts with Razor

As a developer coming from a webforms background, I am continuing my understanding of MVC. A major part of the MVC framework is Razor which is the syntax used within views (Scott Guthrie has a good explanation here). Two components of razor are Layouts and Sections and I will explain these a little further.

Model, View, Controller

Model, View, Controller

From a Webforms perspective, a layout is similar concept to a masterpage (introduced in ASP.Net 2.0). They allow the developer to create coded templates that surround the content; therefore allowing CSS, JS etc to be globally set in the template and be available to the content page once it is rendered. MVC3 uses Layouts in much the same way.

A layout is generally stored in the ~/Views/Sharedfolder of the MVC3 project so it is visible to all views. But you can store it wherever you want because you specify the path to it in the view. You will see what I mean later.

Not only do layouts allow you to globally set stylesheets or include script files, but you can also put individualised view content into areas marked up in the layout page. You can even customise whether to show these areas based on the content in the view.

Let's look at the structure of a layout page. Say we have a layout page called _SiteLayout.cshtml and a view called index.cshtml. Here is the code of _SiteLayout.cshtml.

<!DOCTYPE html>
<html>
<body>
	<header><h1>My Website</h1></header>
	<div>
		@RenderBody()
	</div>
</body>
</html>

The section in this page marked as @RenderBody is where the content from the view gets injected. The entire contents of any view that uses this layout page will be injected into this area of the resulting page.

Here is some example code for the Index.cshtml.

@model MyProject.Model.Entity
@{
	Layout = "~/Views/Shared/_SiteLayout.cshtml";
	View.Title = "My Default View";
}

<h2>My Item</h2>
<ul>
	@foreach (var item in Model)
	{
		<li>@item.name</li>
	}
</ul>

The code line Layout = "~/Views/Shared/_SiteLayout.cshtml"; indicates that this view will use the _SiteLayout.cshtml layout page.

Another feature of layout pages is that you can inject a number of different code blocks from the view into the layout page. In much the same way as the entire contents of the view get inserted into the layout page in the above example, we can specify a block of specially marked code in the view to be injected into a specific area in the layout page. Let's use the same example above, but include a special area that will accept code from the view.

<!DOCTYPE html>
<html>
<body>
	<header><h1>My Website</h1></header>
	<div id="sidebar">
		@RenderSection("SideBar", required: false)
	</div>
	<div>
		@RenderBody()
	</div>
</body>
</html>

The layout page has a dedicated area set aside for code in the view. The view must have a specially marked section which will be injected into the sidebar div element. The view code would look like this.

@model MyProject.Model.Entity
@{
	Layout = "~/Views/Shared/_SiteLayout.cshtml";
	View.Title = "My Default View";
}

@section SideBar {
	<ul>
		<li><a href="#">Navigation Link 1</a></li>
		<li><a href="#">Navigation Link 2</a></li>
		<li><a href="#">Navigation Link 3</a></li>
	</ul>
}

<h2>My Item</h2>
<ul>
	@foreach (var item in Model)
	{
		<li>@item.name</li>
	}
</ul>

Notice that the section blocks have the same name - SideBar. This is the key to this functionality. When the layout page has a @RenderSection block, the contents of a @section block in the view will be injected at the nominated element DOM location. So the view now handles two different areas of content. The content in the @section block gets inserted into the @RenderSection area in the layout and the rest of the view gets inserted into the @RenderBody area of the layout page.

The order that the blocks appear in the view doesn't even matter. You just need to specify the block and it will be inserted in the layout page. You can also specify that the section must be specified in the view by changing the required attribute.

@RenderSection("SideBar", required: true)

You might have mixed content in different pages and therefore don't want the sidebar in every single page. Instead, you may want to put some other "placeholder" text in the page instead of the sidebar. You can do this with the IsSectionDefined function. This function looks into the view to see if the section has been declared. If so, the section is rendered. If not, the layout page can be told to insert some other text. Look at how this would look in our layout page above.

<!DOCTYPE html>
<html>
<body>
	<header><h1>My Website</h1></header>
	<div id="sidebar">
		@if (IsSectionDefined("SideBar")) {
			@RenderSection("SideBar", required: false)
		} else {
			<p>This text replaces the sidebar.</p>
		}
	</div>
	<div>
		@RenderBody()
	</div>
</body>
</html>

I hope that helps you understand layout pages and sections a little more. Feel free to drop a comment if you have any suggestions or experience with these.

Til next time ..