Cache level of property value converters seems to be ignored
See original GitHub issueI’ve tested this in 7.15.0, 8.1.0, 8.4.0 and 8.6.0. It appears that this has never really worked - or at least since 7.15.0, but Umbraco 8 still works differently than Umbraco 7.
The problem appears to be that the value returned by the GetPropertyCacheLevel
method in a property value converter (PVC) is largely ignored.
Umbraco 7
In Umbraco 7, the PropertyCacheLevel
enum is defined with the following values:
-
Content
Indicates that the property value can be cached at the content level, ie it can be cached until the content itself is modified. -
ContentCache
Indicates that the property value can be cached at the content cache level, ie it can be cached until any content in the cache is modified. -
Request
Indicates that the property value can be cached at the request level, ie it can be cached for the duration of the current request. -
None
Indicates that the property value cannot be cached and has to be converted any time it is requested.
But regardless of what value my dummy PVC returns, it appears to always behave as Request. My PVC is implemented as following:
using System;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
namespace CacheLevelTests {
public class CacheLevelTestsValueConverter : IPropertyValueConverterMeta {
public bool IsConverter(PublishedPropertyType propertyType) {
return propertyType.PropertyEditorAlias == "CacheLevelTests";
}
public object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) {
return Guid.NewGuid();
}
public object ConvertSourceToObject(PublishedPropertyType propertyType, object source, bool preview) {
return source;
}
public object ConvertSourceToXPath(PublishedPropertyType propertyType, object source, bool preview) {
return null;
}
public PropertyCacheLevel GetPropertyCacheLevel(PublishedPropertyType propertyType, PropertyCacheValue cacheValue) {
return PropertyCacheLevel.None;
}
public virtual Type GetPropertyValueType(PublishedPropertyType propertyType) {
return typeof(Guid);
}
}
}
For testing, I’ve installed Umbraco with the default starter kit, added a property to the Home content type. I’ve changed the Home.cshtml view to contain the following:
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage<ContentModels.Home>
@using ContentModels = Umbraco.Web.PublishedContentModels;
@{
string value1 = Model.Content.GetPropertyValue<string>("tests");
string value2 = Umbraco.TypedContent(Model.Content.Id).GetPropertyValue<string>("tests");
<pre>@value1</pre>
<pre>@value2</pre>
}
When the cache level is set to None, I would assume value1
and value2
would differ (as the PVC returns a random GUID each time caclled), but they’re always the same. Both variables are updated with the same new random GUID once I reload the page.
But more importantly, regardless of the cache level returned my PVC, the example always shows a new GUID when I reload the page. For both Content and ContentCache, it out to require on editor saving something in the backoffice.
Umbraco 8
In Umbraco 8, the PropertyCacheLevel
enum is instead defined with the following values:
-
Unknown
Not really a lot of description about this one in the documentation 🤷 -
Element
Indicates that the property value can be cached at the element level, i.e. it can be cached until the element itself is modified. -
Elements
Indicates that the property value can be cached at the elements level, i.e. it can be cached until any element is modified. -
Snapshot
Indicates that the property value can be cached at the snapshot level, i.e. it can be cached for the duration of the current snapshot.
In most cases, a snapshot is created per request, and therefore this is equivalent to cache the value for the duration of the request.</remarks> -
None
Indicates that the property value cannot be cached and has to be converted each time it is requested.
So besides the addition of Unknown and that the names have changed a bit for Umbraco 8, the concept is similar to how it worked in Umbraco 7.
But while Umbraco 7 always appears to use Request regardless of the cache level returned by the PVC, Umbraco 8 seems to use Element instead.
This means that regardless of the cache level, it requires the exact element (here the home node) to be updated for the cache to be cleared. Simply reloading the page or updating another page in Umbraco doesn’t change anything.
I tested this in a similar way to Umbraco 7, so here is my dummy PVC:
using System;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
namespace CacheLevelTests {
public class CacheLevelTestsValueConverter : PropertyValueConverterBase {
public override bool IsConverter(IPublishedPropertyType propertyType) {
return propertyType.EditorAlias == "CacheLevelTests";
}
public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) {
return Guid.NewGuid();
}
public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) {
return inter;
}
public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) {
return null;
}
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) {
return PropertyCacheLevel.None;
}
public override Type GetPropertyValueType(IPublishedPropertyType propertyType) {
return typeof(Guid);
}
}
}
And my home.cshtml
views looks as following:
@inherits Umbraco.Web.Mvc.UmbracoViewPage<ContentModels.Home>
@using ContentModels = Umbraco.Web.PublishedModels;
@{
string value1 = Model.Value<string>("tests");
string value2 = Umbraco.Content(Model.Id).Value<string>("tests");
<pre>@value1</pre>
<pre>@value2</pre>
}
Why does this matter?
In Umbraco 7, it appears that the values returned by PVCs are only cached for the scope of a request, which may explain why it hasn’t been discovered before (at least I haven’t found an issue about it).
But for PVCs doing performance heavy lookups, it’s crucial that these values can be cached. So the end result is that Umbraco 7 uses too little caching, which then may affect performance.
On the other hand, Umbraco 8 caches the values too hard. For instance if we have a content picker, it’s PVC specifies the cache level as Elements. This should mean that updating any page in Umbraco should clear the cache.
But as the cache level seems to be treated as Element instead, it requires the specific page with the content picker to be updated.
This then means, that if we make changes to any of the pages selected in the content picker, the changes aren’t reflected on the page with the content picker.
So for a site we’re currently working on, we’re running this code on each publish:
private void ContentService_Published(IContentService sender, PublishEventArgs<IContent> e)
{
var pages = e.PublishedEntities;
foreach (IContent page in pages)
{
try
{
// Use code from internal RefreshAllContentCache https://github.com/umbraco/Umbraco-CMS/blob/13488be0a3280c966ca5115c1cf75cc1979e5819/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs#L107
var payloads = new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) };
Umbraco.Web.Composing.Current.DistributedCache.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads);
}
catch
{
}
}
}
It feels very dirty having to do this, but it’s done out of necessity.
Issue Analytics
- State:
- Created 3 years ago
- Reactions:3
- Comments:7 (3 by maintainers)
I don’t know either, maybe the unit tests for it are a good start? https://github.com/umbraco/Umbraco-CMS/blob/v8/contrib/src/Umbraco.Tests/Published/PropertyCacheLevelTests.cs
I can see we moved from
IPublishedCache
toIPublishedCache2
for 8.7 (https://github.com/umbraco/Umbraco-CMS/pull/8273/), so maybe it’s been improved there?I added this code to refresh cache. After some investigation it appeared that cache is refreshed when we update clientDependency.config file. So I added custom componet for update this file. ` public class ClearCacheOnPublishComponent : IComponent { private readonly ILogger _logger;
}`