Walkthrough: Creating a model-bound view that renders a contents resolver

Abstract

How to create a Sitecore model-bound view that renders the output of a contents resolver.

You use contents resolvers with the Sitecore Layout Service in order to provide more complex data beyond serialization of a component data source. Common use cases include site navigation, search results, or returning the output or state of custom business logic. In this walkthrough, you learn how to create a contents resolver that returns a model of the content tree for you to render in a model-bound view.

When you have completed the walkthrough, we recommend that you familiarize yourself with the Sitecore Helix practices to organize your solution according to the business domain and create a common closure for related code from multiple services. This allows your related content resolvers and rendering code to be co-located within your solution. See the Sitecore Helix Examples repository for inspiration.

Note

To create a contents resolver, you must complete the following procedures:

To create a model-bound view that renders the contents resolver, you must complete the following procedures:

Add the Sitecore.LayoutService NuGet package

The Sitecore.LayoutService NuGet package contains the Sitecore.LayoutService.ItemRendering.ContentsResolvers.RenderingContentsResolver class that the contents resolver class inherits from.

To add the Sitecore.LayoutService NuGet package to the Platform project:

  1. In Visual Studio, in Solution Explorer, right-click Platform/References and click Manage NuGet Packages.

  2. In the top-right corner of the window, in the Package source menu, select the package source that points to https://sitecore.myget.org/F/sc-packages/api/v3/index.json.

  3. In the top-left corner of the window, click Browse and search for the Sitecore.LayoutService NuGet package.

  4. In the list of search results, click Sitecore.LayoutService.

  5. On the right side of the window, click Install to download and install the Sitecore.LayoutService NuGet package.

  6. Allow the the Nuget Package Manager to modify the solution if required.

Add the Templates class

The Templates class is a dependency of the contents resolver class.

To add the Templates class to the Platform project:

  1. In Visual Studio, in Solution Explorer, right click Platform and click Add, New Item.

  2. In the Add New Item window:

    • In the left pane, click Installed/Visual C#/ASP.NET Core/Code.

    • In the middle pane, click Class.

    • At the bottom, in the Name field, enter Templates.cs and click Add.

  3. In the class file, add the following content:

    using Sitecore.Data;
    
    namespace MyProject
    {
      public static class Templates
      {
        public static class SiteRoot
        {
          public static readonly ID Id = ID.Parse("{061CBA15-5474-4B91-8A06-17903B102B82}");
        }
            
        public static class Page
        {
          public static readonly ID Id = ID.Parse("{85CD20BD-9E45-40D1-BB24-9B165667EA7A}");
    
          public static class Fields
          {
            public static readonly ID Title = ID.Parse("{E5365688-B6BB-41AB-AAFB-3215D68507D8}");
          }
        }
      }
    }
  4. Save the class file.

Create the contents resolver class

The contents resolver class overrides the ResolveContents method of the Sitecore.LayoutService.ItemRendering.ContentsResolvers.RenderingContentsResolver class and returns a content tree object.

Important

The ResolveContents method must return an object that can be returned to the rendering host as a JSON rendering.

To create the contents resolver class in the Platform project:

  1. In Visual Studio, in Solution Explorer, right-click Platform and click Add, New Item.

  2. In the Add New Item window:

    • In the left pane, click Installed/Visual C#/ASP.NET Core/Code.

    • In the middle pane, click Class.

    • At the bottom, in the Name field, enter NavigationContentsResolver.cs and click Add.

  3. In the class file, enter the following content:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Sitecore.Abstractions;
    using Sitecore.Data.Items;
    using Sitecore.LayoutService.Configuration;
    using Sitecore.LayoutService.ItemRendering.ContentsResolvers;
    using Sitecore.Mvc.Presentation;
    
    namespace MyProject.LayoutService
    {
      public class NavigationContentsResolver : RenderingContentsResolver
      {
        private const int NavigationDepth = 2;
    
        private readonly BaseLinkManager _linkManager;
    
        public NavigationContentsResolver(BaseLinkManager linkManager)
        {
          _linkManager = linkManager;
        }
    
        public override object ResolveContents(Rendering rendering, IRenderingConfiguration renderingConfig)
        {
          var contextItem = GetContextItem(rendering, renderingConfig);
          var siteRoot = contextItem?.Axes.GetAncestors().FirstOrDefault(item => item.DescendsFrom(Templates.SiteRoot.Id));
          if (siteRoot == null)
          {
            return null;
          }
    
          // First page under our site root should be Home
          return GetNavigation(siteRoot, NavigationDepth).FirstOrDefault();
        }
    
        private IEnumerable<object> GetNavigation(Item parent, int depth)
        {
          depth--;
          return parent.Children
            .Where(item => item.DescendsFrom(Templates.Page.Id))
            .Select(item => new
            {
              Title = item[Templates.Page.Fields.Title],
              Url = _linkManager.GetItemUrl(item),
              Children = depth >= 0 ? GetNavigation(item, depth) : new object[0]
            });
        }
      }
    }
  4. Save the class file.

  5. Right-click the Platform project and click Publish.

  6. In the Platform window, click Publish to publish the Platform project the Sitecore Content Management and Content Delivery instances.

Tip

  • Navigation contents resolvers must avoid using Sitecore.Context.Item or other page-specific context in order to output data on current/active navigation items (like highlighting active navigation items in the navigation menu). This is better handled client-side and ensures that you can cache navigation data across pages.

  • Where possible, use Sitecore Content Search instead of Sitecore Query or other resource-expensive methods of navigating the content tree.

Create the contents resolver item

The contents resolver item links the contents resolver class to Sitecore.

To create the contents resolver item in the Sitecore instance:

  1. In Content Editor, right-click /sitecore/System/Modules/Layout Service/Rendering Contents Resolvers and click InsertRendering Contents Resolver.

    Tip

    To optimize serialization you can organize contents resolvers into folders.

  2. In the Message window, in the Name field, enter Navigation Resolver and click OK.

  3. On the Content tab for the Navigation Resolver item, in the Data section, in the Type field, enter the name of the contents resolver:

    MyProject.LayoutService.NavigationContentsResolver, MyProject
  4. Select Use Context Item to make the contents resolver navigate the content tree relative to the current item (the Sitecore context page/route) when building the content tree model. If not checked, the contents resolver uses its datasource item.

  5. Save the contents resolver item.

Create the JSON rendering item

The JSON rendering item renders the output of the contents resolver class (the content tree object) as JSON formatted data for the rendering host.

To create the JSON rendering item in the Sitecore instance:

  1. In Content Editor, right click /sitecore/Layout/Renderings and click Insert, Json Rendering.

  2. In the Message window, in the Name field, enter Navigation Rendering and click OK.

  3. On the Content tab for the Navigation Rendering item, in the Layout Service section, in the Rendering Contents Resolver field, select the Navigation Resolver item.

  4. Save the JSON rendering item.

Tip

  • If possible, configure output caching for renderings that use contents resolvers. This is especially important for renderings like the navigation example that need to access many items.

Add the JSON rendering item to the Page templates standard values

By adding the JSON rendering item to the Page templates standard values, all pages that inherit from the Page template include the JSON rendering.

To add the JSON rendering item to the Page templates standard values:

  1. In Content Editor, click /sitecore/Templates/Project/MyProject/Page/__Standard Values.

  2. On the Presentation tab, click Details.

  3. In the Layout Details window, in the Default section, click Edit.

  4. In the Device Editor window, on the Layout tab, select Layouts/Project/MyProject/Main.

  5. On the Controls tab, click Add.

  6. In the Select a Rendering window, click Renderings/Navigation Rendering.

  7. In the Add to Placeholder field, enter MyProject-main and click Select.

  8. In the Device Editor window, click OK.

  9. In the Layout Details window, click OK.

Test the contents resolver

Before starting on the model-bound view, test that the Sitecore Layout Service includes the contents resolver JSON rendering in its output to the rendering host.

The rendering host page might output Unknown component or a similar message depending on your Rendering Engine configuration. This is perfectly normal.

In this sample JSON rendering, we have added some items to the content tree to populate the navigation model:

{
  "uid":"19ea6f2e-3ab9-4e9d-b3a6-9a0e5a6e9d0a",
  "componentName":"Navigation Rendering",
  "dataSource":"",
  "params":{},
  "fields":{
    "title":"Welcome to Sitecore",
    "url":"/en/",
    "children":[
      {
        "title":"Main Nav 1",
        "url":"/en/MainNav1",
        "children":[
          {
            "title":"Child Nav 1",
            "url":"/en/MainNav1/ChildNav1",
            "children":[]
          },
          {
            "title":"Child Nav 2",
            "url":"/en/MainNav1/ChildNav2",
            "children":[]
          }
        ]
      },
      {
        "title":"Main Nav 2",
        "url":"/en/MainNav2",
        "children":[
          {
            "title":"More Child Nav",
            "url":"/en/MainNav2/MoreChildNav",
            "children":[]
          }
        ]
      },
      {
        "title":"Main Nav 3",
        "url":"/en/MainNav3",
        "children":[]
      }
    ]
  }
}

Create the model

The model provides a strongly typed class that maps to the data in the contents resolver JSON rendering returned by the Sitecore Layout Service.

To create the model in the RenderingHost project:

  1. In Visual Studio, in Solution Explorer, right-click RenderingHost/Models and click Add, New Item.

  2. In the Add New Item window:

    • In the left pane, click Installed/Visual C#/ASP.NET Core/Code.

    • In the middle pane, click Class.

    • At the bottom, in the Name field, enter NavigationModel.cs.

    • Click Add.

  3. In the class file, to bind the JSON rendering to the model, add the following content:

    using System.Collections.Generic;
    using Sitecore.AspNet.RenderingEngine.Binding.Attributes;
    
    namespace MyProject.Models
    {
      public class NavigationModel
      {
        [SitecoreComponentField]
        public string Title { get; set; }
    
        [SitecoreComponentField]
        public string Url { get; set; }
    
        [SitecoreComponentField]
        public IEnumerable<NavigationModel> Children { get; set; }
      }
    }

    Note

    Because you are not binding directly to Sitecore fields, you can use simple types like string in your model, but you must explicitly attribute them with [SitecoreComponentField].

  4. Save the class file.

Create the model-bound view

The model-bound view renders the model data as HTML.

To create the model-bound view in the RenderingHost project:

  1. In Visual Studio, in Solution Explorer, right-click RenderingHost/Views/Shared/Components/SitecoreComponent and click Add, New Item.

  2. In the Add New Item window:

    • In the left pane, click Installed/Visual C#/ASP.NET Core/Web.

    • In the middle pane, click Razor View.

    • At the bottom, in the Name field, enter Navigation Rendering.cshtml.

      Important

      You get an Unknown Compontent error if the name of the model-bound view file does not match the name of the JSON rendering.

    • Click Add.

  3. In the model-bound view file, add the following content:

    @model NavigationModel
    
    <ul>
      <li><a href="@Model.Url">Home</a></li>
      @foreach (var navigation in Model.Children)
      {
        <li>
        <a href="@navigation.Url">@navigation.Title</a>
        @if (@navigation.Children != null)
        {
          <ul>
          @foreach (var childNav in navigation.Children)
          {
            <li><a href="@childNav.Url">@childNav.Title</a></li>
          }
          </ul>
        }
        </li>
      }
    </ul>
  4. Save the model-bound view file.

Register the model-bound view

You must map the model-bound view to the layout service component name (both are called Navigation Rendering) in the Sitecore Rendering Engine.

To register the model-bound view in the Startup class:

  1. In Visual Studio, in Solution Explorer, double-click the Startup.cs file.

  2. In the Startup.cs file, in the services.AddSitecoreRenderingEngine call, register the model-bound view and the model as a Rendering Engine component with the AddModelBoundView() method:

    services.AddSitecoreRenderingEngine(options =>
      {
        //Register your components here
        options
          .AddModelBoundView<ContentBlockModel>("ContentBlock")
          .AddModelBoundView<NavigationModel>("Navigation Rendering")
          .AddDefaultPartialView("_ComponentNotFound");
      })
      // Includes forwarding of Scheme as X-Forwarded-Proto to the Layout Service, so that
      // Sitecore Media and other links have the correct scheme.
      .ForwardHeaders()
      // Enable forwarding of relevant headers and client IP for Sitecore Tracking and Personalization.
      .WithTracking()
      // Enable support for the Experience Editor.
      .WithExperienceEditor(options =>
      {
        // Experience Editor integration needs to know the external URL of your rendering host,
        // if behind HTTPS termination or another proxy (like Traefik).
        if (Configuration.RenderingHostUri != null)
        {
          options.ApplicationUrl = Configuration.RenderingHostUri;
        }
      });

    Important

    You get an Unknown Component error if the name of the model-bound view file does not match the name of the JSON rendering.

  3. Save the Startup.cs file.

Test the model-bound view

Tip