Within Umbraco, content can be accessed in two possible ways, either via the ContentService
which returns IContent
or via the ContentCache
which returns IPublishedContent
. The main difference with these is that IContent
comes straight from the database, and all of it's properties are stored in a raw format, where as IPublishedContent
comes from a cache layer with all it's properties stored in a more accessible and strongly typed manor.
Generally speaking, when working with content in Umbraco, if you are reading / rendering content, you'll want to use IPublishedContent
from the ContentCache
, and if you are creating / updating content you'll want to use IContent
and the ContentService
.
TL;DR: Just show me the solution!
The Problem
There are times however when content may not yet be available in the ContentCache
, such as in a content Saving/Saved event handler, and so it would be great if, as a last resort, we could fallback to fetching the IContent
version from the database and use that instead.
The issue with this however is that IContent
and IPublishedContent
are very different interfaces especially around content property access, where with IContent
you are generally dealing with a raw format, often JSON instead of the friendlier strongly typed access you get from IPublishedContent
.
In these situations you need to do one of two things, either create code that works in two different ways depending on the model you are working with, but this often requires you to have insider knowledge as to how the raw properties are formatted, or, we can implement some kind of wrapper to convert the IContent
interface into IPublishedContent
and have all the contents properties converted to their strongly typed implementation in the same way that Umbraco does internally. The benefit of this approach is that we only have to write code that deals with one particular interface and we don't have to worry about the property raw data formats.
This then is the approach I decided to take.
In an ideal world, it would be great if Umbraco had an API that could do this for you out of the box, after all, it must be doing this already when it converts the IContent
to IPublishedContent
in order to add it to the ContentCache
. Unfortunately however, this is not the case (not publicly anyway), and so we must go about implementing a solution ourselves.
The Research
Strike 1
Before taking a stab at this problem myself I thought I'd do a quick search to see if the problem had already been solved.
The first promising result I came across was a forum post on the Umbraco Developer Portal (Our), where the accepted answer points to a Gist by Jeroen Breuer in which he creates an IContent
extension method that wraps the IContent
instance in a custom IPublishedContent
implementation that does all the relevant conversions.
PERFECT!
Not so fast!
Under closer inspection it appeared that it only seemed to work for Umbraco v7 installs and I needed a solution for Umbraco v8+ so unfortunately it wasn't a goer.
Strike 2
As my search continued, I noticed that David Peck had recently tweeted asking the very same question.
Could this be the answer?
Nope again!
In the thread, David was basically discouraged from doing this, and really I can understand why as the whole point of the caching layer is to ensure the site stays performant and using IContent
instead of IPublishedContent
is one sure fire way of bringing a site to a halt if used improperly.
Regardless, if used properly, I still think being able convert IContent
to IPublishedContent
is a useful feature in certain circumstances, so I continued with my quest.
At this point I started to poke around the Umbraco source code trying to find where it does it's own conversion, and that's when I came across the PublishedContentHashtableConverter
and specifically it's PagePublishedContent
implementation
The more I looked at it, the more I thought "This looks pretty close to Jeroen's code. Could I just update what Jeroen had and make it work for Umbraco v8? ๐ค"
And so, I DID ๐
The Solution
What you'll find below then is essentially a cross between the code from Jeroen's original Gist and elements from the PagePublishedContent
in order to bring it inline with the Umbraco v8 API.
It may look like a lot of code, but ultimately all it's doing is taking an IContent
element, and wrapping it in an IPublishedContent
custom implementation that intercepts all of it's field / method requests and runs the same code Umbraco does during a conversion including property value conversion.
It does this in a Lazy way too meaning it should only run the expensive code if you ask for the given property, and once it's run, it remembers the result so it won't need to run the same code again.
For me this is exactly what I needed and given I knew at least one person was looking for an answer to the same problem, I thought I might as well throw it up on a blog post, and so here it is.
I hope this helps
๐จ DISCLAIMER ๐จ
I mentioned it a little previously, but working with IContent
is not the most performant way to work with Umbraco content, so if you do use this method, be sure to know what you are doing.
I won't be held responsible for any problems you introduce into your solution by using this. Use this code at your own risk.
Top comments (1)
Unfortunately it doesn't seem like this can be bound to a view.
Cannot bind source content type id.Core.Extensions.ContentExtensions+PublishedContentWrapper to model type Umbraco.Web.PublishedModels.GenericContentPage. The view model is a ModelsBuilder type, but the source is not. The application is in an unstable state and should be restarted