Nirman
Nirman

Reputation: 6783

How to use "RenderBody()" in partial view when the partial view is shared across layout pages?

Following is the exact scenario in my ASP.NET MVC application:

There are two layout pages which are quite identical to each other. But one is having angular related attributes in "" tag, whereas other is a non-angular layout. In order to avoid duplicate markup in both razor layout files, I thought to create a partial view and share it across the layout pages.

Following is the partial view (razor), I have named it as "_LayoutPartial":

_LayoutPartial.cshtml

<div class="navbar navbar-inverse navbar-fixed-top">
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            @Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li>@Html.ActionLink("Home", "Index", "Home")</li>
                <li>@Html.ActionLink("About", "About", "Home")</li>
                <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
            </ul>
            @Html.Partial("_LoginPartial")
        </div>
    </div>
</div>
<div class="container body-content">
    @RenderBody()
    <hr />
    <footer>
        <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
    </footer>
</div>

The above partial view is shared in "_Layout.cshtml", and "_AngularLayout.cshtml" which are as per below:

_Layout.cshtml

<body>
    @Html.Partial("_LayoutPartial")

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>

_AngularLayout.cshtml

<body ng-app="myAngularLab">
    @Html.Partial("_LayoutPartial")

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>

When I try to run the MVC application, I get following error:

The file "~/Views/Shared/_LayoutPartial.cshtml" cannot be requested directly because it calls the "RenderBody" method.

The error message is very obvious, and it seems that we can use RenderBody method only in the master page, and not anywhere else. But I am keen to know how we can have two identical layout pages (with a little differences as shown in the example) by writing common code instead of keeping duplicate code in both layout pages?

Upvotes: 3

Views: 9317

Answers (2)

Tetsuya Yamamoto
Tetsuya Yamamoto

Reputation: 24957

I think you need to use "nested layout" by putting one layout page as "master page" for other layouts, similar to webforms counterpart:

_BaseLayout.cshtml

<html>
<head>
    <!-- other header tags (link, meta, title etc.) -->

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")

    <!-- JS & CSS includes if available -->
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <!-- other elements -->
    </div>
    <div class="container body-content">
        @RenderBody()
        <!-- other elements -->
    </div>

    @RenderSection("scripts", required: false)
</body>
</html>

_Layout.cshtml

@{ Layout = "~/Views/Shared/_BaseLayout.cshtml"; } // put reference to base layout here

<div>
    <!-- Non-Angular layout -->
    @RenderBody()
</div>

_AngularLayout.cshtml

@{ Layout = "~/Views/Shared/_BaseLayout.cshtml"; } // put reference to base layout here

<div ng-app="myAngularLab">
    <!-- Angular layout -->
    @RenderBody()
</div>

Advantages using "nested layout":

  1. Eliminates the need to repeat @Styles.Render & @Scripts.Render, also for @RenderSection (they're inserted automatically for every pages referencing the base layout).

  2. Eliminates the need to use more than one body tag, just replace with div tag to hold view page contents.

The file "X" cannot be requested directly because it calls the "RenderBody" method certainly originated from Html.Partial which called directly from child layouts, where a layout page with RenderBody method can't be requested directly as a template.

If you want to set default layout for a nested layout, put one of the child layout reference in _ViewStart.cshtml:

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

Another note: Partial views are intended to use in view pages, not in layout pages. Layout pages are specifically used as placeholder for view pages - not for directly accessed by action methods.

Upvotes: 5

Ahmad
Ahmad

Reputation: 455

Try this in _Layout.cshtml:

<body>
    @Html.Partial("_LayoutPartial")

    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
   <div class="container body-content">
    @RenderBody()
    <hr />
    <footer>
        <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
    </footer>
</div>
</body>

and remove the @RenderBody() from _LayoutPartial.cshtml.

@RenderBody() is commonly used in layout pages, it renders the portion of a content page that is not within a named section.

Hope this is helpful:)

Upvotes: 0

Related Questions