Sitecore Layouts with MVC Inheritance

      No Comments on Sitecore Layouts with MVC Inheritance

Part of what makes Sitecore a great platform for developers is that there is often more than one solution for a requirement.  At the presentation level, one common need is to have similar page layouts while having specific differences between templates and other situations.  You may have seen this handled this way.

This is the Presentation Layout Details for item of the template “Professional”.  The layout is called “SiteLayout” and the only rendering, inserted into the placeholder “content”, is called “Professional Detail Controller”.

Without showing all the markup, you can assume something like this for the layout:

<html>
<head>
  <!--code to populate meta tags-->
  <!--code referencing CSS, JS, etc.-->
</head>
<body>
  <div class="main-content">
    @Html.Sitecore().Placeholder("content")
  </div>
</body>
</html>

And the rendering being used might look something like this:

<div class="professional">
  <div class="professional--image">@Html.Glass().Editable(Model, x => x.Image)</div>
  <div class="professional--name">@Html.Glass().Editable(Model, x => x.FirstName) @Html.Glass().Editable(Model, x => x.LastName)
  <!--lots more fields and other markup-->
</div>

There’s nothing particularly wrong with this approach.  However, the relationship between the outer and inner markup is all defined within Sitecore which may not be immediately obvious to a new developer when they join the project.  Also, if you wanted to do further nesting, that would also need to be handled through more renderings and placeholders.  For example, if you wanted all of your separate all your landing and detail pages into similar structures.

Elsewhere…

For developers new to Sitecore but well versed in Microsoft MVC and Razor, it might feel like there’s something missing: layout inheritance. Normally in a “vanilla” MVC application, each view designates a Layout property towards the top of the file. In that referenced file, there’s a called to RenderBody() which injects the results of the original view. It behaves a lot like placeholder but there’s a few other features as well. For one, you can define “sections” in your layout making for several different places the view can inject content. There is also the ViewBag which is a dynamic object that allows the view to exchange data with the layout. An example combination might look like this.

Layout

<html>
<head>
  <title>@ViewBag.HtmlTitle | Example</title>
  <!--code referencing CSS, JS, etc.-->
</head>
<body class="@ViewBag.BodyClass">
  <div class="main-content">
    <h1>@RenderSection("PageTitle")</h1>
    @RenderBody()
  </div>
</body>
</html>

View

@{
  Layout = "~/Areas/Example/Views/Shared/_Layout.cshtml";
  ViewBag.HtmlTitle = string.Format("{0} {1}", Model.FirstName, Model.LastName);
  ViewBag.BodyClass = "professional-detail";
}

@section PageTitle {
  @Model.FirstName @Model.LastName
}

@Model.Biography

In this scenario, the view is passing the HTML title and body CSS class to the layout. The page title (h1) is comprised of the first and last name fields. Lastly, the actual contents of the page is populated by the biography field.

Best of Both Worlds

Turns out, you can do the very same thing in Sitecore. Let’s make a small change to our layout.

<html>
<head>
  <title>@ViewBag.HtmlTitle | Example</title>
  <!--code referencing CSS, JS, etc.-->
</head>
<body class="@ViewBag.BodyClass">
  <div class="main-content">
    <h1>@RenderSection("PageTitle")</h1>
    @Html.Sitecore().Placeholder("pre-body")
    @RenderBody()
    @Html.Sitecore().Placeholder("post-body")
  </div>
</body>
</html>

And incorporate some Glass Mapper syntax to our view to support the Experience Editor.

@{
  Layout = "~/Areas/Example/Views/Shared/_Layout.cshtml";
  ViewBag.HtmlTitle = string.Format("{0} {1}", Model.FirstName, Model.LastName);
  ViewBag.BodyClass = "professional-detail";
}

@section PageTitle {
  @Html.Glass().Editable(Model, x => x.FirstName) @Html.Glass().Editable(Model, x => x.LastName)
}

@Html.Glass().Editable(Model, x => x.Biography)

In the layout, we now have two placeholders before and after the body, so we can still support renderings being injected in those places. Plus, we now have formally stated, through code, the relationship between to these files and their markup. This especially handy for developers who might only be focused on the front-end side of the application and might be less experienced with Sitecore. Also, something like the ViewBag.BodyClass is right here in the markup as opposed to set somewhere in the model or controller.

If we followed through on this approach, we would make a .cshtml file and accompanying layout item (under /sitecore/layout/Layouts), likely in a one-to-one relationship to the Sitecore templates we are using.

One Step Further

As I hinted earlier, we can actually create multiple levels between layouts and views. I’ll return to my example of landing and detail pages. Let’s keep our layout as-is but add two new files.

_Landing.cshtml

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

@section PageTitle {
  @RenderSection("PageTitle")
}

<div class="landing-body">
  @RenderBody()
</div>

_Detail.cshtml

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

@section PageTitle {
  @RenderSection("PageTitle")
}

<div class="detail-body">
  @RenderBody()
</div>

We skip setting the ViewBag values, waiting for the view to set them. We redefine the PageTitle section with the same name. If all the items had a common field, we could output it and skip that step. Lastly, we wrap a nested call to RenderBody().

If we update our previous view to point to _Detail.cshtml instead of _Layout.cshtml, it will get the extra wrapper div tag with the “detail-body” class. Now as we work through the rest of the project, we’ll see an obvious association of all of our landing and detail pages in the code.

Conclusion

Many architects might see this solution and believe that we really haven’t accomplished anything. Instead of one layout and 12 renderings we now have 12 layouts. But I argue having all of this outlined in the code itself (and not behind Sitecore configuration) is incredibly helpful for front-end developers or more junior developers transitioning from conventional Microsoft MVC. Like in so many other cases, code that is obvious and self-documenting is often its own reward.

But by all means, if you disagree, feel free to comment below.

Leave a Reply