How to access Composition properties on an IPublishedContent object
3rd April, 2024
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.
You can create a composition by selecting "Composition" on the create a Document Type menu:
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.
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.
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!