How to access Composition properties on an IPublishedContent object

3rd April, 2024

Owain Jones

Umbraco's compositions are perfect for creating reusable properties across DocTypes, but how do we access these properties on an IPublishedContent object?

Image generated with DALL·E

A few days a go, a colleague asked me "How do I know if an IPublishedContent uses a specific composition?", and after going through it with them, I thought the answer might make a nice blog post! So hopefully someone else finds this info useful too! 😄

Firstly, what is a composition?

According to the Umbraco Documentation:

Compositions provide a way to create reusable sets of properties that can be added to one or more document types. This can help simplify the management and consistency of content types across your website.

Source: https://docs.umbraco.com/umbraco-cms/fundamentals/data/defining-content/default-document-types#compositions

You can create a composition by selecting "Composition" on the create a Document Type menu:

Screenshot of the Umbraco Backoffice, showing the "Create DocType" menu with options for creating "Document Type with Template", "Document Type", "Element Type", "Composition", and "Folder". The "Composition" option is highlighted.
A screenshot of the "Create a Document Type" menu in the Umbraco backoffice.

What this option does, is it creates a DocType without a template, pre-sets a default icon, and sets the "Is an Element Type" property to true.

Why use compositions?

Compositions in Umbraco are awesome! I use them all the time for components/properties that will be used on multiple different DocTypes, which usually include SEO properties, navigation settings, search settings and hero banner properties; this has the benefit of only defining these properties once and avoids duplicating them across multiple DocTypes, which would be a headache to maintain!

Hero Banner Example

Here's an example of a Hero Banner composition DocType, and in this scenario, our Homepage, Landing Page, and Content Page DocTypes all compose this composition.

A screenshot of the Umbraco CMS backoffice interface displaying the 'Hero Banner Composition' DocType. The left side shows a navigation menu with 'Document Types', 'Blocks', 'Compositions', 'Lists', 'Pages', 'Settings', 'Media Types', and 'Member Types'. 'Hero Banner Composition' is highlighted under 'Compositions'. In the main pane, there are input fields labeled 'Hero Banner Title' and 'Hero Banner Subtitle' with text areas, and 'Hero Banner Image' with a media picker, all marked to 'Vary by culture'.

Ok, that's cool, but how can we access the properties of a composition?

If you're using Models Builder, you can simply access these properties on your strongly typed page model in your view.

// Model is a generated Models Builder object, e.g. "ContentPage"
@Model.HeroBannerTitle

But what if you have an IPublishedContent instead of a strongly typed page model? And how do you check if it even composes the composition?

In this scenario, you can use "if is" to check and cast the IPublishedContent object into the composition's generated interface:

// Model is an IPublishedContent object
if (Model is IHeroBannerComposition heroBanner)
{
    var title = heroBanner.HeroBannerTitle;
}

or alternatively, an "if is not":

// Model is an IPublishedContent object
if (Model is not IHeroBannerComposition heroBanner) { return; }
var title = heroBanner.HeroBannerTitle;

This is possible because the generated models, of the DocTypes that use this composition, will implement the composition's interface.

A screenshot of the generated Homepage model class, showing it implementing the IHeroBannerComposition interface

Here's an example of how we could use this in a ViewComponent

Let's take the above Hero Banner example, and make a ViewComponent for it:

public class HeroBannerViewComponent : ViewComponent
{
    public IViewComponentResult Invoke(IPublishedContent model)
    {
        if (model is not IHeroBannerComposition heroBanner)
        {
            return Content(string.Empty);
        }

        var viewModel = new HeroBannerViewModel 
        {
            Title = heroBanner.HeroBannerTitle.IsNullOrWhiteSpace()
                    ? model.Name
                    : heroBanner.HeroBannerTitle,
            Subtitle = heroBanner.HeroBannerSubtitle,
            ImageUrl = heroBanner.HeroBannerImage?.GetCropUrl(CropAliasConstants.HeroCrop) 
        }

        return viewModel .ImageUrl == null
                ? View("HeroBannerSmall", viewModel)
                : View("HeroBanner", viewModel);
    }
}

As you can see in the code above, we first check if the IPublishedContent object implements the IHeroBannerComposition interface, and if it does, we build a view model and pass it to one of two possible Hero Banner views.

We can then invoke this view component by using a Tag Helper in our page views:

@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<ContentModels.Homepage>
@{
    Layout = "_MainLayout";
}

<vc:hero-banner model="Model"></vc:hero-banner>

@await Html.GetBlockListHtmlAsync(Model.BlockList)

Neat!