ASP.NET Core Rendering SDK
View types and Model binding

ASP.NET Core Rendering SDK <br/>View types and Model binding

While MVC version of Sitecore provided two rendering types (View Rendering and Controller Rendering), Sitecore Headless propose only one - JSON Rendering. But there is a choice on Rendering Host side instead - you can choose out of tree: Model-Bounded View, View Component or Partial View.

View Types

Partial Views

The most easiest option and it supposed to be used in cases where you don’t need much logic or access to content, so, maybe some layout parts that defines placeholders and/or wraps content in global wrapping tags. Basically, all you need to create is a razor view, so, kind of View Rendering analog.

Partial views may be registered with AddPartialView method in Rendering Engine configuration and in most cases you’ll pass component name and view path as attributes. There is AddDefaultPartialView method that’s recommended to be used to render some error content when there is something wrong with locating component’s View.

builder.Services
    .AddSitecoreRenderingEngine(options =>
    {
        options
            .AddPartialView("ComponentName", "ViewName")
            .AddDefaultPartialView("_ComponentNotFound");
    })

To access content inside a partial view you can use razor extensions

  • SitecoreRoute() - get the current Sitecore Route object.

  • SitecoreContext() - get the current Sitecore Context object.

  • SitecoreComponent() - get the current Sitecore Component object.

For example, we have a page with “MetaTitle” field and component that has “Title” and “Content”. We can get all this data inside of a Partial View, even convert data for component into strongly typed object of type ContentBlock defined additionally by ourselves:

@using Rendering.Models;
@using Sitecore.LayoutService.Client.Response.Model.Fields

@{
  var routeData = this.SitecoreRoute();
  var contextData = this.SitecoreContext();
  var componentData = this.SitecoreComponent().ReadFields<ContentBlock>();
  var pageTitleField = route.ReadField<TextField>("MetaTitle");
}

<section>
  <h1 asp-for="@pageTitleField"></h1>
  <h1>Site name: @contextData.Site.Name</h1>
  <h2 asp-for="@componentData.Title"></h2>
  <div>
    <sc-text asp-for="@componentData.Content"></sc-text>
  </div>
</section>

View Components

This type sits on other side and provides strongly typed models for razor views as well as code-behind logic, so, it’s something that close to Controller Renderings. Instead of MVC controllers this type is based on View components.

To map a rendering you must use “AddViewComponent” that may have some variations

builder.Services
    .AddSitecoreRenderingEngine(options =>
    {
        options
          .AddViewComponent("MyComponent", "ViewComponentExample")
          .AddViewComponent(name => name.StartsWith("sc"), "Other");
    });

To bind a model in your view component, you can either inherit from Sitecore.AspNet.RenderingEngine.Mvc, BindingViewComponent and invoke BindView, or inject the Sitecore.AspNet.RenderingEngine.Binding.IViewModelBinder service and invoke an overload of Bind.

Model-Bounded View

This one is something in the middle of previous options and nothing else then special case of View Component. It has strongly typed model but no code-behind logic - it’s mapped to default SitecoreComponentViewComponent component.

To map a rendering you must use “AddModelBoundView“ method that may have some variations:

builder.Services
    .AddSitecoreRenderingEngine(options =>
    {
        options
          .AddModelBoundView<ContentBlockModel>("ContentBlock")
          .AddModelBoundView<GenericBlockModel>(name => name.StartsWith("sc"), "GenericBlock")
    });

This is a generic method, so, you have to specify a model type as well.

Model Binding

Model Binding are based on ASP.NET Core principles and it’s pretty close to the way of how [FromBody] attribute works. Response from Layout Service contains information about Context and Route. This may be changed and additional data may be included in response using custom-written provider, but changing will be reviewed in future posts.

First time you may meet with model binding is data in SitecoreController and it’s parameter of type Route from namespace Sitecore.LayoutService.Client.Response.Model. You can bind data from Layout Service to your model using special attributes from namespace Sitecore.AspNet.RenderingEngine.Binding.Attributes. Let’s review some simple example of data received from Layout Service:

{
  "sitecore": {
    "context": {
      "pageEditing": false,
      "site": {
        "name": "GettingStarted"
      },
      "pageState": "normal",
      "language": "en",
      "itemPath": "/"
    },
    "route": {
      "name": "Home",
      "displayName": "Home",
      "fields": {
        "Title": {
          "value": "Home Page"
        },
        "Content": {
          "value": ""
        }
      },
      "databaseName": "master",
      "deviceId": "fe5d7fdf-89c0-4d99-9aa3-b5fbd009c9f3",
      "itemId": "798c26f5-65c8-4e12-af39-9d4695df9601",
      "itemLanguage": "en",
      "itemVersion": 1,
      "layoutId": "6340552c-88b9-4b5d-988a-555797decb0e",
      "templateId": "e7c58f30-7481-446a-822a-1fc47995dc64",
      "templateName": "Page",
      "placeholders": {
        "body": [
          {
            "uid": "c441224e-eded-4009-9478-098b304c42a2",
            "componentName": "TestRendering",
            "dataSource": "{36EFA2A4-D4BC-404E-BD11-ACB99F73EFEE}",
            "params": {},
            "fields": {
              "Text Field": {
                "value": "Text Field Content"
              }
            }
          }
        ]
      }
    }
  }
}

As you can see, Context data provides general information about site and request and Route data provides information about context item and it’s components (including datasources data).

Let’s create a binding model for some of this data to get understanding of using binding attributes and types:

using Sitecore.AspNet.RenderingEngine.Binding.Attributes;
using Sitecore.LayoutService.Client.Response.Model.Fields;

namespace Rendering.Models;

public class CustomModel
{
    [SitecoreContextProperty]
    public string ItemPath { get; set; }          // "/"
    
    [SitecoreRouteProperty]
    public string DatabaseName{ get; set; }       // "master"
    
    [SitecoreRouteProperty(Name = "Name")]
    public string PageName { get; set; }          // "Home"

    [SitecoreComponentField(Name = "Text Field")]
    public TextField SingleLineText { get; set; }
}

Note:

  • Binding Properties names should start with uppercase letter, so, while in JSON we have “name” in model “Name” should be specified.

  • Binding Properties in most cases will be mounted to simple types such as string or bool, but you can bind whole Context data to Sitecore.LayoutService.Client.Response.Model.Context type using [SitecoreContext] attribute. Some of cases may be found in official documentation.

  • Fields should be mapped to appropriate classes from Sitecore.LayoutService.Client.Response.Model.Fields namespace to provide proper editing functionality and extension methods