Good day to all. Recently, I have been actively developing ASP.NET MVC 3 & Razor for a “difficult” web application and today I came across a problem that experienced developers may have already researched and solved, but the newcomers will find the information below, I hope and I hope useful.
Description of the problem
Let the application have a couple of views: View.cshtml and ViewWithSide.cshtml, and there are still two master pages: Layout.cshtml and LayoutWithSide.cshtml, the first being the master page for the second. As you can guess from the file names, XxxWithSide.cshtml adds a sidebar in the page, the output format of which is defined in the master page, and the insides in the view. In the main master page Layout, in addition to the main markup, the output of the “navigation” section is defined, which is specified in the views.
And when the “navigation” section is defined in the ViewWithSide code, and not in the LayoutWithSide, because this section should be processed by the “next” Layout page, then when you try to open the ViewWithSide, an application will get an error:
The following sections have already been defined but have not been rendered for the layout page "~ / Views / Shared / LayoutWithSide.cshtml": “navigation” (The “navigation” section is defined, but not shown anywhere in the master page) .
')
The idea of solving this problem is quite simple: you need to transfer the output of this section to the “next” master page, and then let them figure it out.
Some 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>
Naturally, I expected that Razor and ASP.NET MVC themselves will understand and display the section in the master page where it is needed. But alas and ah ... However, there is a problem, there is an idea how to solve - it is necessary to solve.
Search solutions
In my case, the “navigation” section is not just optional, and if it is not defined, then some more markup is not displayed. For this reason, simply to get the section of the same name in LayoutWithSide and bring the content transferred from the view into it would not have rolled.
I tried to wrap the declaration of the section inside
if (IsSectionDefined("navigation"))
, maybe ... I didn’t
if (IsSectionDefined("navigation"))
hopes on this method - it didn’t work (the analyzer just does not expect such a construction and “Parser Error” scolds it).
A quick and superficial search on MSDN and the Internet did not give anything useful. But in the methods available inside the view, the method
DefineSection (string name, SectionWriter action) was noticed immediately.
Since it was not possible to wrap the if section declaration in the style of Razor, you can try to wrap the creation section from C # code. It turned out like this:
if (IsSectionDefined("navigation")) { DefineSection("navigation", delegate() { Write(RenderSection("navigation")); }); }
And this code successfully worked and completed the task.
Decision
Of course, I didn’t stop at that, but I don’t have to write
so many letters such code for each section that I need to pass to the master page.
We have at our disposal
fashionable, convenient extensible C # methods. As a result, 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 it is connected to the project, it is enough to call the method, passing it the names of the necessary sections. 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 transfer several sections to the master page, then you can call this.
this.PropagateSections("section1", "section2", "section3")
, you understand ...