Local Data

Just the webpages that make up a website

Utilizing the tree stucture of Umbraco content, you create a one to one relationship between webpages that are created by Content Editors and webpages that are served up to End Users. Maintaining this link is vital for keeping your content simple. Its simple to explain to content editors, its easy to understand when a node becomes a webpage because all local nodes are webpages. At no point do local nodes have a different or secondary purpose.

  • If its a local node then its a webpage
  • Delete a local node, deletes a webpage
  • Create a node within a parent node, adds a webpage with a new slug appended to the url of the parent webpage

Local is the webpages of a website.

Page Compositions

Common Properties for your Local pages

There are no rules to what is or isn't common Document Type properties that all your local webpages should use, but its safe to suggest that its likely that there should be some, even if its just ones to do with SEO or Internal page linking.

This allows for the code base to have standardised methods to be used to render all the local webpages, as well as one code block to create sitemap.xml files too.


Composition Examples

Examples of Composition that you might need for every local webpage

Seo

Example Seo properties for every page

This allows content editors to adjust SEO on a page per page basis, though its regularly abused if content editors are not familiar with the underlying concepts.

Example of common Seo properties are:-

  • Enable: Allow page to be indexed by search engines
  • Title: Seo Title of page (Defaults to Page Name if blank)
  • Description: Description of page for search engines
  • Keywords: Most important words for search engines to notice on this page. Don't use same keywords across all pages. Seperate different keywords with commas
  • Date Published: Select a date for when this page was orginally published. If empty will use date this page was created
  • Priority: How important is this page compared to others pages within this site. Search engines use this value to rank your pages with similar keywords/content. Setting all pages to the same value has no effect and is counter-productive

These properties are added to a composition and then that composition is then included in every webpage Document Type 

Internal Linking

Referencing webpages within the same website 

Its very common to what to create links between your local webpages. This is so end users can move around your website and find the content they might be looking for as well as other things that take their interest. Those linkings can be done with a simple text string but its more popular to use images, title and maybe a synopsis / abstract of the other pages to more entice end users.

To faciliate this then local webpages have to contain an image, title and/or synopsis for those links to work.

The Title can be reused from the existing Page Name or Seo Title (if present). Also Synopsis could reuse other existing properties like Seo Description or Body Text (if present).

In this example the image is referred to as a Thumbnail, because when displaying it, its very likely to be a smallish image that you require to display.  

  • Thumbnail: Image of this page that is used to represent this page when linked from other pages
  • Title: Heading for page or use Page Name if blank
  • Synopsis: Short description of this page

These properties are added to a composition and then that composition is then included in every webpage Document Type 

Display

Common properties that are used to display content

This example very much depends on how you wish to configure your website. It used to be common a few years back to create a large Rich Text Editor property called BodyText and allow Content Editors to edit away. The downside to this, is that the output is rather boring in today's environment especially when rendering embedded images, bulletted lists with headers and subtitles all on multiple devices with varying sizes.

Its more common these days to try to generate content as reusable blocks, how those blocks are edited varies from situation to situation; Grid Editors and Block List Editor allow Content Editors to deploy reusable blocks that can been viewed in situ where as solutions based on Nested Content (or variants like Contentment) allow easier integration into existing front-end templates by removing any backend rendering.


This Global Manifesto site itself uses Contentment to control all content, though thats very much a personal preference based on factors to do with wishing to use an existing Front-End template (that was downloaded for free from the web) and happy to have no in-situ rendering of content.

Code

Example Code to help consume Global Manifesto

Create a Service that can be injected into your code and razor templates that helps to understand Global Manifesto concepts like HomePage(), Website(), Breadcrumb()

public interface IPresentationService
{
  ModelsBuilder.Site Site { get; }
  ModelsBuilder.PageStandard Home { get; }
  IEnumerable<Tuple<string, string>> Breadcrumb(IPublishedContent content);
}
public class PresentationService : IPresentationService
{
  private const string SiteCacheKey = "104d0d13-6f5a-4d25-8266-e1ab15dcfbd4";
  private const string HomeKeyCacheKey = "fe5992d2-7601-4c9a-b59d-debad90cb016";
  private const string HomeCacheKey = "d5c3793e-6f16-4353-a4e0-d24d6a72a805";

  private readonly IHttpContextAccessor httpContextAccessor;
  private readonly IUmbracoContextFactory umbracoContextFactory;
  private readonly IPublishedValueFallback publishedValueFallback;

  public PortfolioService(IHttpContextAccessor _httpContextAccessor, IUmbracoContextFactory _umbracoContextFactory, IPublishedValueFallback _publishedValueFallback)
  {
    httpContextAccessor = _httpContextAccessor;
    umbracoContextFactory = _umbracoContextFactory;
    publishedValueFallback = _publishedValueFallback;
  }

  /* Returns current WebSite for current web request */
  public ModelsBuilder.Site Site
  {
    get
    {
      var site = httpContextAccessor.HttpContext.Items[SiteCacheKey] as ModelsBuilder.Site;
      if (site == null)
      {
        using (var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext())
        {
          site = new ModelsBuilder.Site(umbracoContextReference.UmbracoContext.PublishedRequest.PublishedContent.Ancestor(ModelsBuilder.Site.ModelTypeAlias), publishedValueFallback);
          httpContextAccessor.HttpContext.Items[SiteCacheKey] = site;
        }
      }
      return site;
    }
  }

  /* Returns the current HomePage Id for current web request */
  private Guid? HomePageKey
  { 
    get
    {
      var home = httpContextAccessor.HttpContext.Items[HomeKeyCacheKey] as Guid?;
      if (home == null)
      {
        home = Site.GetProperty(Umbraco.Cms.Core.Constants.Conventions.Content.InternalRedirectId)?.Value<GuidUdi>(publishedValueFallback)?.Guid;
        httpContextAccessor.HttpContext.Items[HomeKeyCacheKey] = home;    
      }
      return home;
    }
  }

  /* Returns the current HomePage for current web request */
  public ModelsBuilder.PageStandard HomePage
  {
    get
    {
      var home = httpContextAccessor.HttpContext.Items[HomeCacheKey] as ModelsBuilder.PageStandard;
      if (home == null)
      {
        using (var umbracoContextReference = umbracoContextFactory.EnsureUmbracoContext())
        {
          var home = ModelsBuilder.PageStandard(umbracoContextReference.UmbracoContext.Content.GetById(HomePageKey));
          httpContextAccessor.HttpContext.Items[HomeCacheKey] = home;
        }
      }
      return home;
    }
  }

  /* Returns the Breadcrumb in format (Item1 = Page Name, Item2 = Page Url) for current web request */
  public IEnumerable<Tuple<string, string>> Breadcrumb(IPublishedContent content)
  {
    var breadcrumb = new List<Tuple<string, string>>();
    var first = true;
    while (content != null && content.Key != Site.Key)
    {
      var seo = (IPageSeo) content;
      breadcrumb.Add(new Tuple<string, string>(string.IsNullOrWhiteSpace(seo.SeoTitle) ? content.Name : seo.SeoTitle, first ? null : content.Url()));
      first = false;
      content = content.Parent;
    }
    return breadcrumb.AsEnumerable().Reverse();
  }
}
/* Standard Umbraco Code found at \Views\_ViewImports.html */
@using Umbraco.Extensions
@using Umbraco.Cms.Web.Common.PublishedModels
@using Umbraco.Cms.Web.Common.Views
@using Umbraco.Cms.Core.Models.PublishedContent
@using Microsoft.AspNetCore.Html
@using Stubborn.Infrastructure.Presentation.Exhibit
@using Stubborn.Infrastructure.Constitution
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Smidge
@inject Smidge.SmidgeHelper SmidgeHelper

/* Custom Code */
@inject YourNamespace.IPresentationService Presentation
@* Example breadcrumb implementation *@
<ol>
  @foreach (var breadcrumb in Presentation.Breadcrumb(Model))
  {
    <li>
      @if (breadcrumb.Item2 != null)
      {
        <a href="@breadcrumb.Item2">@breadcrumb.Item1</a>
      }
      else
      {
        <span>@breadcrumb.Item1</span>
      }
    </li>
  }
</ol>

All above examples are for Umbraco v10