We love feedback from you on our products and the problems in your daily work that you would like us to solve. Please describe the challenge you're encountering and your desired outcome. Be as detailed as possible.
For technical issues or bugs please head to Support or our Developer Community. You can assign up to 20 votes in total. Thank you for your feedback.
Status explanation: 'Future Consideration' = Continuing to collect further feedback, not planned at this time. 'Investigating' = Prioritized for deeper customer and feasibility investigations ahead of planning development.
To make a simple showcase, imagine the following code for caching a friendly URL for a page.
CacheManager.Insert
($"GetFriendlyUrl({contentLink.ID})",
contentLink.GetFriendlyURL(),
new CacheEvictionPolicy
(TimeSpan.FromDays(1),
CacheTimeoutType.Absolute,
null,
new string[] { GetVersionKey(contentLink)})
);
Where we have a helper method:
public static string GetVersionKey(this ContentReference contentLink)
{
var _contentVersionRepository = ServiceLocator.Current.GetInstance<IContentVersionRepository>();
var _cacheKeyCreator = ServiceLocator.Current.GetInstance<IContentCacheKeyCreator>();
var versionCacheKey = string.Empty;
var latestPublishedVersion = _contentVersionRepository
.List(contentLink)
.ToList()
.FirstOrDefault(v => v.Status == VersionStatus.Published);
if (latestPublishedVersion != null)
{
versionCacheKey = _cacheKeyCreator.CreateVersionCacheKey(latestPublishedVersion.ContentLink);
}
return versionCacheKey;
}
If this VersionKey is not in the cache, the code above will store a null object for that masterkey.
The side effect is when trying to edit this page/IContent the frontend hangs because of expected json is 500 error html page, instead of expected json result.
epi.js:2 SyntaxError: Unexpected token '<'
at Object.dojo.fromJson (dojo.js:15:82234)
at Object.json (dojo.js:15:120242)
at _497 (dojo.js:15:123066)
at _2cd (dojo.js:15:73012)
at _2cb (dojo.js:15:72704)
at dojo.Deferred.resolve.callback (dojo.js:15:73525)
at dojo.js:15:125114
at _2f2 (dojo.js:15:75368)
at _2ec (dojo.js:15:75232)
at _2f1.resolve (dojo.js:15:76693)
Which in turn is because of CmsContentContextResolver.GetCommonDraft
=> ContentProvider.LoadContentFromCacheOrRepository
=> ... => ObjectInstanceCacheExtensions.TryGetWithWait
is expecting an cached item of key EPContentVersion:29154_797799
being IContent
or not existing in cache at all. But the null object is considered an System.Object.
System.InvalidOperationException: Unknown object of type System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 found with cache key EPContentVersion:29154_797799. Expected type EPiServer.Core.IContent, EPiServer, Version=11.20.11.0, Culture=neutral, PublicKeyToken=8fe83dea738b45b7
at EPiServer.Framework.Cache.ObjectInstanceCacheExtensions.TryGetWithWait[T](IObjectInstanceCache cache, String cacheKey, T& instance)
at EPiServer.Framework.Cache.ObjectInstanceCacheExtensions.ReadThroughWithWait[T](IObjectInstanceCache cache, String cacheKey, Func`1 readValue, Func`2 evictionPolicy)
at EPiServer.Core.Internal.ContentInstanceCache.ReadThrough(ContentReference contentLink, String selectedLanguage, Func`1 readValue)
at EPiServer.Core.ContentProvider.LoadContentFromCacheOrRepository(ContentReference contentreference, ILanguageSelector selector)
at EPiServer.Core.Internal.ProviderPipelineImplementation.GetItem(ContentProvider provider, ContentReference contentLink, LoaderOptions loaderOptions)
at EPiServer.Core.Internal.DefaultContentLoader.TryGet[T](ContentReference contentLink, LoaderOptions loaderOptions, T& content)
at EPiServer.Core.Internal.DefaultContentLoader.Get[T](ContentReference contentLink, LoaderOptions loaderOptions)
at EPiServer.Cms.Shell.UI.Rest.Internal.CmsContentContextResolver.GetCommonDraft(ContentReference contentReference, String language, ContentLanguageInformation& languageInformation)
at EPiServer.Cms.Shell.UI.Rest.Internal.CmsContentContextResolver.TryResolveUri(Uri uri, ClientContextBase& instance)
at EPiServer.Shell.UI.Rest.ContextStore.ResolveUri(Uri uri)
at lambda_method(Closure , ControllerBase , Object[] )
One would think that a TryGetWithWait
wouldn't throw an exception like this, the try
-part kind of indicates that it's error handled internally, this leads me to think there is an bug in the implementation, that some cases aren't handled correctly.
But, one could also argue that adding a masterkey dependency would mean that there would be a cascade-delete when the masterkey is removed from cache, regaradless if the masterkey was in cache or not. One would think of this as something that is called when changing content version, like publishing a new version etc., this code would indicate that this cache item should be deleted. But that's maybe a bad assumption?
We have found workarounds for now, like not adding masterkey dependency if there is no cached item for that masterkey, and instead caching a shorter time without masterkey dependency.
CMS 11.20.14.0
and .NET 4.7.2
Hi Cindy, and Henrik!
Thanks for the response.
I feel that there is missing some clarification about this in the documentation, at https://docs.developers.optimizely.com/content-cloud/v12.0.0-content-cloud/docs/object-caching there could be a complementary section, although the last section of Master keys indicates this explanation a bit, it would be nice with some section with a new header, describing how to cache custom stuff with content link dependency, and what to absolutely not do. ;)
Hi Jonas,
I asked one of CMS engineers to look into this and received the following response (they can't seem to post public comments, hence me passing it down).
"Hi Jonas,
I think that there has been a slight misunderstanding on what the master keys in the
CacheEvictionPolicy
are and how to use theIContentCacheKeyCreator
to depend on a content cache entry. Obviously it's never good when something results in an unhandled exception, but I think that we should try to solve your problem first.First of all we should establish that when something is inserted into the cache with cache dependency keys, it will be removed when any of the keys does not exist in the cache. This means that it will removed with the dependent item is removed, but it also means that it will be removed immediately if it doesn't exist at the time of the insert.
Another thing we should note is that the underlying HttpRuntime Cache (in CMS 11) only has one type of cache dependency keys, while the CMS Cache has two types of dependency keys. CacheKeys works just as the normal ones, while MasterKeys has a slightly different behavior. When inserting something with a master key dependency, the cache will make sure that this key exists in the cache, and if it doesn't a dummy object will be inserted with the master key with no expiration. This feature was introduced to support clearing the cache of a certain class of items, e.g. "clear the content type cache". Cache keys that represents actual items should never be used as MasterKeys.
So in your case when you are inserting your friendly URL with a master key that matches a content cache key, and that item does not already exist in the cache, you will be inserting a dummy object in the cache that will essentially prevent that content item from loading properly.
So I would suggest that you instead use the version key as a normal dependency key. And if you want to have the ability to clear all the friendly urls from the cache you can provide a static master key dependency.
There should also not be any need for your GetVersionKey helper method. You can just use the
IContentCacheKeyCreator.CreateCommonCacheKey
to get the shared key for the published version, or in your case since the friendly URL will be language specific, you could probably useIContentCacheKeyCreator.CreateLanguageCacheKey
.Your code should therefore look something like:
"
He also mentioned that if you want to discuss this further, you can reach him on Slack. It is Henrik Nyström. :)
Complementary info
This example is pretty real world. It was on article pages, and the 5 latest article siblings where presented in a side menu.
When edited pages that where in the menu on some sibling, like visit some article, click on another and the enter edit mode, these article pages didn’t enter edit mode, the whole edit pane was broken.
If I remember correctly pages not in menu was able to edit, no broken edit mode.