Silversuit

Using Umbraco ModelsBuilder with Partials

I learned something useful you can do with Umbraco's ModelsBuilder and partials recently so I thought I'd write it up here as a reference for myself and others who might be searching for it.

ModelsBuilder

I won't go into the details of what ModelsBuilder does, or how it does it. If you do need to get up to speed with it, you can read Dave's excellent article or the new ModelsBuilder series on Umbraco.tv. I'll wait here while you do that. When you're back, read on.

A word about templates

In this article my Umbraco templates are inheriting from the newer UmbracoViewPage view instead of the more familiar default UmbracoTemplatePage. I've done this because UmbracoViewPage does not use dynamics. I consider abstinence from dynamics in Umbraco, as of now, good practice, especially as it will be the default state of affairs in Umbraco 8. It can also improve performance. Read more about the differences in another of Dave's articles.

ModelsBuilder vs magic strings

The great thing about ModelsBuilder is that it gives us access to strongly typed properties. Instead of the following, which involves the error-prone process of specifying a magic string and explicitly converting to the correct type:

@Model.GetPropertyValue<string>("cardTitle")

We can do this:

@Model.CardTitle

Much more tidy and easy to read, plus less error prone.

ModelsBuilder + Partials

But what about partials? I had been struggling to see how I could use ModelsBuilder models with them. The whole point of a partial is to DRY up your code. They are supposed to be used in different templates and, as long as the model has properties that match those referenced in the partial, and we don't use ModelsBuilder's elegant syntax, then all is good. For example, if we were using dynamics we could do something like this:

@inherits UmbracoTemplatePage
@{
    var cardTitle = CurrentPage.CardTitle;
}

As long as the model contains a property with an alias of cardTitle, then it'll render.

If we're sticking with the non-ModelsBuilder strongly typed way, we'd do the same like this:

@inherits UmbracoViewPage
@{
    var cardTitle = Model.GetPropertyValue<string>("cardTitle")
}

This is fine, but it's really just a more verbose version of the dynamic version. It's a bit of a fudge. The "cardTitle" is a magic string and comes with all the problems and brittleness that go along with it. You don't get any IntelliSense or IDE help, which means, for instance, if you've made a typo or the property name has changed, the IDE won't tell you.

So how do we get strongly typed ModelsBuilder properties into our partial?

The key is for the partial to know what properties will be handed to it ahead of execution. It's straightforward to do this with a normal, non-partial template: we pass in the ModelsBuilder generated model. For example, if we have a document type called "Topic Content", ModelsBuilder will have generated a model class called TopicContent we can pass into the view like this:

@inherits UmbracoViewPage<TopicContent>

You can do the same with a partial and, as long as it's not used on any page other than a Topic Content one, then we're fine. But what if we also want to use the partial on an "About" or a "News" page? Let's flesh out this example before we continue.

Example

Say we have a module on our site called "Related Link" (it's a bit contrived to have only one related link, I know, but it serves as an example). It allows editors to enter a related link that will be rendered to the page in the appropriate place with the appropriate styles. The related link consists of a linkTitle, linkBodyText, and linkUrl. All are simple text strings. The HTML that gets rendered will be the same no matter what page it appears on, therefore it's an ideal candidate for a partial.

In Umbraco, given we want to include the module on several pages, we can implement it as a component and mix it into our Topic Content, About, and News doctypes using Document Type Compositions.

Now, here's the thing. When we've got a composition, ModelsBuilder generates an interface for each component. For our Related Link component, which has a doctype alias of relatedLink, ModelsBuilder will create an interface called IRelatedLink. We can pass this interface into our partial, and we'll get our strongly typed properties. Example:

@inherits UmbracoViewPage<IRelatedLink>
@{
    var linkTitle = Model.LinkTitle;
    var linkBodyText = Model.LinkBodyText;
    var linkUrl = Model.LinkUrl;
}

<div class="related-links">
    <h3><a href="@linkUrl">@linkTitle</a></h3>
    <p>@linkBodyText</p>
</div>

Thus, wherever we compose our Related Link component into our document types, we know we can use our partial in the associated template. This works quite nicely at a conceptual level. Partials can map neatly to components, making for a tidy design and mental model.

Unfortunately, this tidiness breaks down if you need to use properties from different components or from the base document type as you can only pass in one model. However, with a bit of forethought, you can work around this limitation.

The other limitation is when you need to use a recursive property lookup. To do that, you need to revert back to using GetPropertyValue, like this:

Model.GetPropertyValue<string>("linkTitle", true)

But that's not a huge problem, and Stephan has hinted there will be a strongly typed lambda way of doing the same in future, which might look something like this:

Model.GetPropertyValue(x => x.LinkTitle, recurse: true);

In terms of pure number of characters to type, it's not a huge improvement over the current way of doing it, but you do get IntelliSense, and IDE error reporting and assistance, which to me is well worth it. Unfortunately, as of 7.6, we can't do this kind of lambda goodness, so we'll have to wait a bit longer. But certainly, the promise of getting away from brittle strings and into that bright, strongly typed future is looking closer than ever.

So when you're designing your document types think about how you might want to reuse properties. Consider splitting them out into components so that they can be used anywhere as a composition and inserted into a view via a partial. Every single property on a doctype can be implemented as a composition, so there's nothing to stop you creating some really elegant, strongly typed solutions.