Optional Razor Sections with Default Content
Solution quick links:
RenderSection
The new ASP.NET page framework built on Razor (which is available in MVC 3) provides a facility for content pages to contribute named fragments of markup to their layout pages which the layout page can then render in an arbitrary location using the RenderSection
method.
For example, the following content page declares an "ExtraContent" section:
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
@section ExtraContent {
<div>Some extra content</div>
}
<div>The main content</div>
And the following layout page renders it:
<!DOCTYPE html>
<html>
<head></head>
<body>
@RenderBody()
@RenderSection("ExtraContent")
@RenderSection("OptionalContent", required: false)
</body>
</html>
You can even declare that a section is not required like the “OptionalContent” section in the example above.
But what if you want to have some default content for your optional sections?
Option 1: Use the IsSectionDefined method
The IsSectionDefined
method returns true if a child content page defined a section. You can use that to decide whether to render a section or some other content:
<!DOCTYPE html>
<html>
<body>
@RenderBody()
@if (IsSectionDefined("OptionalContent")) {
@RenderSection("OptionalContent")
}
else {
<div>Default content</div>
}
</body>
</html>
Just remember that you need to use the @RenderSection()
syntax (you need the @ character) so that the contents of the section is actually printed to the output. Without that character you will get an exception with the following message:
The following sections have been defined but have not been rendered for the layout page "~/Views/Shared/_Layout.cshtml": "OptionalContent"
Option 2: Use Razor inline templates
Razor provides a way to pass inline markup templates into function calls. This is a powerful mechanism that allows you to combine custom markup fragments with general purpose display logic. In order to make the following example work you will need to drop the following code into your project:
using System.Web.WebPages;
using System;
public static class SectionExtensions {
private static readonly object _o = new object();
public static HelperResult RenderSection(this WebPageBase page,
string sectionName,
Func<object, HelperResult> defaultContent) {
if (page.IsSectionDefined(sectionName)) {
return page.RenderSection(sectionName);
}
else {
return defaultContent(_o);
}
}
}
This code essentially wraps the previous example in a reusable extension method. All you need now is to provide the default content markup. With it you can write views like the following:
<!DOCTYPE html>
<html>
<body>
@RenderBody()
@this.RenderSection("OptionalContent",
@<div>Default content</div>)
</body>
</html>
For the second parameter you need to use the @ character to trigger the Razor parser to go into markup mode. Once in markup mode you can do anything that you do in the main body of the page.
Just remember that since this is an extension method you need to call it off the this
object. Otherwise the C# compiler will not be able to locate it and you will get a compilation error.
If you like this technique read the follow up: Razor, Nested Layouts and Redefined Sections
Comments
Anonymous
December 14, 2010
Very helpful! Thanks a lot for this article!Anonymous
December 16, 2010
This is quite helpful, thanks. The other day, without knowing about your technique, I ended up doing this instead (basically, you just create helpers to wrap html blocks you'd like to pass in, and pass those helpers into your higher-level helper as a Func<HelperResult>): <div id="result-picker-container"> @resultPicker(html: Html, pickerId: "positive-results", title: "My Positive Results", listWriter: () => positiveResult()) @resultPicker(html: Html, pickerId: "negative-results", title: "My Negative Results", listWriter: () => negativeResult()) </div @helper resultPicker(HtmlHelper<MyModel> html, string pickerId, string title, Func<HelperResult> listWriter){ <div id="@pickerId" class="result-picker"> <h4>@title</h4> @listWriter() </div> } @helper negativeResult() { ... (my negative-result-specific content here) } @helper positiveResult() { ... (my positive-result-specific content here) }Anonymous
January 12, 2011
Hello, why i'm getting the following error when i don't want to have a default content? The following sections have been defined but have not been rendered for the layout page "~/Views/Shared/_Layout.cshtml": "detailueberschrift". I wrote following code in my _Layour.cshtml: @if (ViewContext.RouteData.Values["Action"].ToString() == "Details" || ViewContext.RouteData.Values["Action"].ToString() == "Edit") { <div id="abtrennung"> <h2>Details zum Auftrag @RenderSection("detailueberschrift", required: false)</h2> </div> <!-- abtrennung--> } else if (ViewContext.RouteData.Values["Action"].ToString() == "Edit") { <div id="abtrennung"> <h2>Auftrag bearbeiten</h2> </div> <!-- abtrennung--> } When the Action is not equal "Details" or "Edit" i don't want to show the div. Regards, floatAnonymous
January 13, 2011
float, the error you are seeing happens because you've defined a section in the content page but are not rendering it in the layout. It's flagged as an error to help you avoid misspelled section names etc. What you could do is call the RenderSection method but do nothing with the output: @{ RenderSection("name"); }Anonymous
February 22, 2011
Good article. What is the purpose of the object parameter in defaultContent and _o? I removed _o and the object parameter so that defaultContent has the signature Func<HelperResult> and everything appears to work ok without it.Anonymous
March 11, 2011
How to redefine all the parent layout's sections? It would be more useful to "port them over" than not being able to use them in the child page/layout.Anonymous
August 09, 2011
You could add this signature to the helper in the case you want to use Html.Partial() has the default content: <code>public static HelperResult RenderSection(this WebPageBase webPage, string name, IHtmlString defaultContents) { if (webPage.IsSectionDefined(name)) { return webPage.RenderSection(name); } var result = new HelperResult((x) => x.Write(defaultContents.ToString())); return webPage.RenderSection(name, (x) => result); }</code>Anonymous
August 06, 2013
RenderSection("Footer"); was wrong so @RenderSection("Footer"); saved my day. thank you