LibrarySites.Banner

Sitecore 7: Rendering Fields

This blog post explains how you optimize performance and scalability by retrieving data from a search index when possible or from items in a database when necessary in version 7 of the Sitecore ASP.NET web Content Management System (CMS).

Two associated goals of Sitecore 7 are scalability and performance. One technique for increasing performance reduces access to objects that represent records in relational databases, including classes such as the Sitecore.Data.Items.Item class and its derivatives that represent Sitecore items. As an alternative, solutions should retrieve data from search indexes whenever possible. You can configure which values Sitecore stores in the index for use in populating your POCOs (Plain Old C# Objects) that represent items in a database, and use those POCOs without accessing items in a Sitecore database directly.

There are a few cases where this approach does not work. Beyond the need to ensure that the index is as fresh as it needs to be, some field values require processing to support features such as expansion of dynamic links and inline editing in the Page Editor. Dynamic links typically appear in Rich Text field and can include anchors (HTML <a> elements) and images (HTML <img> elements) that reference other Sitecore items. Only the following field types support inline editing in the Page Editor, though the renderField pipeline can add field commands and potentially other features for fields, including user interfaces such as Preview and the Sitecore debugger.

There seem to be three categories of field types:

  • For most field types, such as Multilist, Droptree, Treelist, and so forth, as well as many field types not often used for presentation (such as Profile Cards, Rendering Datasource, Rules, and so forth), which support neither dynamic links nor inline editing or other features in the Page Editor, you can choose whether to store values in the index or retrieve that data from the items in the database, and you do not need to implement any logic specific to user interfaces such as the Page Editor. For fields that let the user select zero or more Sitecore items, your code can retrieve that data from the search index or from items in the database.
  • Technically, Single-Line Text and Multi-Line Text fields could contain dynamic links, but this is very unlikely. In addition to numeric and Date field types, you can store the values of these fields in the index, but you should retrieve them using the renderField pipeline at least when the user is inline editing in the Page Editor.
  • For some field types, such as General Link, Image, and Rich Text, you should probably not store the value in the index, at least not without modification (conversion to HTML and expansion of dynamic links). At the expense of resources at indexing time, you could implement a computed index field to store field values after the expansion of dynamic links (potentially by invoking the renderField pipeline, though you may need to set the context site first). At least when inline editing in the Page Editor, and of course if you do not store such field values in the index, you should use the renderField pipeline to retrieve them from the item.

While you should consider performance in the Page Editor, it is not as important as performance in the content delivery environment. You could have an instinct to optimize by not invoking the renderField pipeline multiple times or for field types that support dynamic links but not inline editing or only when editing inline in the Page Editor. I would recommend that in the Page Editor, you err on the side of invoking the renderField pipeline excessively frequently rather than too rarely.

All that being said, we need some way to sometimes retrieve data from the index and sometimes from items in the database. There are numerous potential approaches, such as creating a type that knows how to render fields, using a framework like glass, and using various dependency injection frameworks to construct and populate objects, and even MVC-specific solutions such as the mvc.getModel pipeline. This blog post simply demonstrates the technique without the complexity of a specific framework.

Assume we have a data template that contains four fields: Title (a Single-Line Text field), Text (a Rich Text field), Image (an Image field), and Date (a Datetime field). We want to get data from the index whenever possible, except for the Text field (which we do not expect to store a rendered value). We can implement a class based on the following example to represent this type of data.

namespace Sitecore.Sharedsource.ContentSearch.SearchTypes
{
  using System;
  using CS = Sitecore.ContentSearch;
  using SC = Sitecore;
  
  public class ContentItem : CS.SearchTypes.SearchResultItem
  {
    private string _title;
  
    private string _image;
  
    private DateTime? _date;
  
    private SC.Data.Items.Item _item;
  
    public string Title
    {
      get
      {
        if (SC.Context.PageMode.IsPageEditorEditing
          || string.IsNullOrEmpty(this._title))
        {
          return SC.Web.UI.WebControls.FieldRenderer.Render(
            this.Item,
            "Title");
        }
  
        return this._title;
      }
  
      set
      {
        this._title = value;
      }
    }
  
    public DateTime Date
    {
      get
      {
        return this._date.Value;
      }
  
      set
      {
        this._date = value;
      }
    }
  
    public string RenderedDate
    {
      get
      {
        if (SC.Context.PageMode.IsPageEditorEditing
          || this._date == null)
        {
          return SC.Web.UI.WebControls.FieldRenderer.Render(
            this.Item, 
            "Date",
            "format=F");
        }
  
        return this._date.Value.ToString("F");
      }
    }
  
    public string Image
    {
      get
      {
        if (SC.Context.PageMode.IsPageEditorEditing
          || string.IsNullOrEmpty(this._image))
        {
          return SC.Web.UI.WebControls.FieldRenderer.Render(
            this.Item,
            "Image");
        }
  
        return this._image;
      }
  
      set
      {
        this._image = value;
      }
    }
  
    public string Text
    {
      get
      {
        return SC.Web.UI.WebControls.FieldRenderer.Render(
          this.Item,
          "Text");
      }
    }
  
    private SC.Data.Items.Item Item
    {
      get
      {
        if (this._item == null)
        {
          this._item = this.GetItem();
        }
  
        return this._item;
      }
    }
  
    public string GetUrl()
    {
      return SC.Links.LinkManager.GetItemUrl(this.Item);
    }
  }
}

The Title property exposes the rendered value of the Title field, retrieved from the index when possible or the item when needed. When we retrieve values from the item, we do not cache them without consideration; the same value is unlikely to be used twice on a page, is already in the item cache if needed again, might need to support inline editing in one context but not in another, and for other reasons might need to be regenerated on each use.

The Image property exposes the rendered value of the Image field, retrieved from the index if available or the item when needed. The Text property exposes the rendered value of the Text field, always retrieved from the item. The GetUrl() method returns the URL of the item, which is not in the index in the 130424 release but should be in a near-term release.

Under the default configuration, the value of the storageType attribute of the /configuration/sitecore/contentSearch/fieldMap/fieldTypes/fieldType elements named date, datetime, and rich text in the Web.config file prevent you from using such fields to populate your POCOs. The /configuration/sitecore/contentSearch/fieldMap/fieldNames/fieldType elements named title and text override that value for those two fields. For the Datetime field, you can either override the value of the storageType attribute of the /configuration/sitecore/contentSearch/fieldMap/fieldTypes/fieldType elements named datetime to cause Sitecore to store the values for all Datetime fields in the index, or you can add a /configuration/sitecore/contentSearch/fieldMap/fieldNames/fieldName element named date to configure the index to store that individual field value. You can use a Web.config include file such as the following to implement the latter of these two approaches.

<configuration xmlns:patch="https://www.sitecore.com/xmlconfig/">
  <sitecore>
    <contentSearch>
      <configuration>
        <DefaultIndexConfiguration>
          <fieldMap>
            <fieldNames hint="raw:AddFieldByFieldName">
              <fieldName fieldName="date" storageType="YES" indexType="TOKENIZED" vectorType="NO" boost="1f" type="System.DateTime" settingType="Sitecore.ContentSearch.LuceneProvider.LuceneSearchFieldConfiguration, Sitecore.ContentSearch.LuceneProvider" />
            </fieldNames>
          </fieldMap>
        </DefaultIndexConfiguration>
      </configuration>
    </contentSearch>
  </sitecore>
</configuration>

Your class could expose properties that have nothing to do with Sitecore items, whether retrieving that data from other computed index fields for the item, or through web services or other interfaces that access that data directly. So our POCOs can expose at least four types of members:

  • Values always retrieved from the index
  • Values retrieved from the index when possible or the item otherwise
  • Values always retrieved from the item
  • Values calculated or retrieved from other systems

The following sample code sorts by the DateTime representation of the date field but renders an HTML value.

foreach (SC.Sharedsource.ContentSearch.SearchTypes.ContentItem result in
  context.GetQueryable<SC.Sharedsource.ContentSearch.SearchTypes.ContentItem>()
  .Filter(item => item.Language == SC.Context.Language.CultureInfo.TwoLetterISOLanguageName)
  .Filter(item => item.Path.StartsWith("/sitecore/content/home"))
  .OrderByDescending(item => item.Date)
  .Take(10))
{
  output.WriteLine(string.Format(
    "<h4><a href=\"{0}\">{1}</a> : {2}</h4>{3}{4}<br />",
    result.GetUrl(),
    result.Title,
    result.RenderedDate,
    result.Text,
    result.Image));
}

This solution makes extensive use of the fact that Sitecore maps data template field names to index field names, which is reason enough to think carefully when naming your data template fields.

Resources