Home ASP Razor: output sections in master pages master pages

Razor: output sections in master pages master pages

by admin

Good day to all.Since recently I am actively developing on ASP.NET MVC 3 Razor "complicated" web application and today I came across a problem, which experienced developers may have already researched and solved, but for beginners the information below, I think and hope, will be useful.

Problem description

Suppose an application has a pair of views : View.cshtmland ViewWithSide.cshtml, and there are two master pages : Layout.cshtmland LayoutWithSide.cshtml, with the first being the master page for the second. As you can guess from the file names, XxxWithSide.cshtml adds a sidebar to the page whose output format is defined in the master page and the insides in the view. The main Layout master page defines the output of the "navigation" section, which is defined in the views, in addition to the main markup.
And so when the "navigation" section is defined in the ViewWithSide code, but not in LayoutWithSide, because this section should be handled by the "next" master page (Layout), then when you try to open ViewWithSide in the application you will get an error : The following sections have been defined but have not been rendered for the layout page "~/Views/Shared/LayoutWithSide.cshtml": "navigation" (The "navigation" section is defined, but is not displayed anywhere in the master page.)
The idea to solve this problem is quite simple: you need to pass the output of this section to the "next" master page, and let them sort it out themselves.

A bit of code

Layout.cshtml

<!DOCTYPE html><html><head><title> @ViewBag.Title</title></head><body><div>@if (IsSectionDefined("navigation")){ <div class="navigation"> @RenderSection("navigation")</div>}@RenderBody()</div> </body></html>

LayoutWithSide.cshtml

@{Layout = "~/Views/Shared/Layout.cshtml";}<div class="side"> @RenderSection("side", required:false)</div><div class="main"> @RenderBody()</div>

View.cshtml

@{ViewBag.Title = "view";Layout = "~/Views/Shared/Layout.cshtml";}@section navigation {<a href="@Url.Action("Page1")"> page1</a> |<a href="@Url.Action("Page2")"> page2</a>}<h2> viewWithoutSide</h2><div> Main content</div>

ViewWithSide.cshtml

@{ViewBag.Title = "viewWithSide";Layout = "~/Views/Shared/LayoutWithSide.cshtml";}@section navigation {<a href="@Url.Action("Page1")"> page1</a> |<a href="@Url.Action("Page2")"> page2</a>}@section side {<strong> side content</strong>}<h2> viewWithSide</h2><div> Main content</div>

It’s only natural that I expected Razor and ASP.NET MVC to figure it out and display the section in the master page where it’s needed. But alas and ah… However, there’s a problem, there’s an idea how to solve it – we need to solve it.

Search for a solution

In my case, the "navigation" section is not just optional, but if it is not defined, some other piece of markup is not output. So just putting a section with the same name into LayoutWithSide and outputting the content passed from the view to it wouldn’t work.
I tried the section announcement to wrap inside if (IsSectionDefined("navigation")) I didn’t pin my hopes on this method and it didn’t work (the analyzer just doesn’t expect this construct and says "Parser Error" about it).
A cursory search on MSDN and the Internet turned up nothing useful. But in the methods available inside the view, the following method was immediately spotted DefineSection(string name, SectionWriter action)
Since we couldn’t wrap the Razor-style section declaration in an if, we can try to wrap the section creation from the C# code. It works like this :

if (IsSectionDefined("navigation")){DefineSection("navigation", delegate() { Write(RenderSection("navigation")); });}

And this code successfully worked and accomplished the task at hand.

Solution

Of course, I didn’t stop there, I couldn’t write so many letters this code for each section to pass to the master page.
We have at our disposal fancy handy extensible C# methods. As a result, I wrote the following class :

public static class WebPageHelpers{public static void PropagateSection(this WebPageBase page, string sectionName){if (page.IsSectionDefined(sectionName)){page.DefineSection(sectionName, delegate() { page.Write(page.RenderSection(sectionName)); });}}public static void PropagateSections(this WebPageBase page, params string[] sections){foreach (var s in sections)PropagateSection(page, s);}}

And after connecting it to the project, all you have to do is call the method by passing the names of the sections you want. Then LayoutWithSide.cshtml will look like this :

@{Layout = "~/Views/Shared/Layout.cshtml";this.PropagateSection("navigation");}<div class="side"> @RenderSection("side", required:false)</div><div class="main"> @RenderBody()</div>

And if you need to pass several sections to the master page, you can call this.PropagateSections("section1", "section2", "section3") , you get the idea…

You may also like