Back to Home

Razor: displaying sections in master pages of master pages

asp.net mvc 3 · Razor · layout · section

Razor: displaying sections in master pages of master pages

    Good day to all. Recently, I have been actively developing on ASP.NET MVC 3 & Razor a "difficult" web application, and today I came across a problem that experienced developers may have already researched and solved, but for beginners, the information below, I think and hope, turns out to be useful.

    Description of the problem


    Let the application have a couple of views: View.cshtml and ViewWithSide.cshtml, and there are also 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 to the page, the output format of which is defined in the master page, and the insides in the view. In the main Layout master page, in addition to the main layout, the output of the “navigation” section is defined, which is set in the views.

    And so, when the “navigation” section is defined in the ViewWithSide code, but not in the LayoutWithSide, because this section should be processed by the “next” master page (Layout), an error will be generated when trying to open the ViewWithSide in the application: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 on 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 there let them understand.

    Some code


    Layout.cshtml
    @ViewBag.Title
    @if (IsSectionDefined("navigation")) { } @RenderBody()

    LayoutWithSide.cshtml
    @{
        Layout = "~/Views/Shared/Layout.cshtml";
    }
    
    @RenderSection("side", required:false)
    @RenderBody()

    View.cshtml
    @{
        ViewBag.Title = "view";
        Layout = "~/Views/Shared/Layout.cshtml";
    }
    @section navigation {
        page1 |
        page2
    }
    

    viewWithoutSide

    Main content

    ViewWithSide.cshtml
    @{
        ViewBag.Title = "viewWithSide";
        Layout = "~/Views/Shared/LayoutWithSide.cshtml";
    }
    @section navigation {
        page1 |
        page2
    }
    @section side {
        side content
    }
    

    viewWithSide

    Main content

    Naturally, I expected that Razor and ASP.NET MVC will figure it out 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 for a solution


    In my case, the “navigation” section is not just optional, but if it is not defined, then some more piece of markup is not displayed. For this reason, simply entering the section of the same name in LayoutWithSide and displaying the contents transferred from the view into it would not have worked.

    I tried to wrap the section declaration inside if (IsSectionDefined("navigation")), maybe ... I didn’t have any particular hopes for this method - it didn’t work (the analyzer just does not expect such a design and swears “Parser Error” on it).

    A quick and superficial search on MSDN and the Internet did not yield anything useful. But in the methods available inside the view, the DefineSection method (string name, SectionWriter action) was immediately noticed

    Since it was not possible to wrap the section declaration in the Razor style in the if declaration, you can try to wrap the section creation from C # code. It turned out like this:
    if (IsSectionDefined("navigation"))
    {
        DefineSection("navigation", delegate() { Write(RenderSection("navigation")); });
    }
    

    And this code successfully worked out and completed the task.

    Decision


    Of course, I didn’t stop there, not to write so many letters of such code for each section that needs to be transferred to the master page.
    We have at our disposal fashionable convenient extension methods C #. 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 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");
    }
    
    @RenderSection("side", required:false)
    @RenderBody()

    And if you need to transfer several sections to the master page, you can call this.PropagateSections("section1", "section2", "section3"), well, you understand ...

    Read Next