Skip to content

Commit e7ec903

Browse files
author
Warren Buckley
committed
Try to simplify it by grabbing the cachekey that is used in the base class CacheTagHelper from Microsoft
Track that rerference/key in our dictionary/list which we can remove from the IMemoryhCache on ContentPublished, Media & Dictionary with CacheRefresher notifications
1 parent 65a2fdf commit e7ec903

File tree

3 files changed

+64
-108
lines changed

3 files changed

+64
-108
lines changed
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using Our.Umbraco.TagHelpers.Notifications;
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Our.Umbraco.TagHelpers.Notifications;
3+
using Our.Umbraco.TagHelpers.Services;
24
using Umbraco.Cms.Core.Composing;
35
using Umbraco.Cms.Core.DependencyInjection;
46
using Umbraco.Cms.Core.Notifications;
@@ -10,9 +12,11 @@ public class CacheTagHelperComposer : IComposer
1012
// handle refreshing of content/media/dictionary cache notification to clear the cache key used for the CacheTagHelper
1113
public void Compose(IUmbracoBuilder builder)
1214
{
13-
builder.AddNotificationHandler<ContentCacheRefresherNotification, HandleContentCacheRefresherNotification>();
14-
builder.AddNotificationHandler<MediaCacheRefresherNotification, HandleMediaCacheRefresherNotification>();
15-
builder.AddNotificationHandler<DictionaryCacheRefresherNotification, HandleDictionaryCacheRefresherNotification>();
15+
builder.Services.AddSingleton<IUmbracoTagHelperCacheKeys, UmbracoTagHelperCacheKeys>();
16+
17+
builder.AddNotificationHandler<ContentCacheRefresherNotification, CacheTagRefresherNotifications>();
18+
builder.AddNotificationHandler<MediaCacheRefresherNotification, CacheTagRefresherNotifications>();
19+
builder.AddNotificationHandler<DictionaryCacheRefresherNotification, CacheTagRefresherNotifications>();
1620
}
1721
}
1822
}
Lines changed: 32 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
using Our.Umbraco.TagHelpers.CacheKeys;
2-
using System;
3-
using Umbraco.Cms.Core.Cache;
1+
using Microsoft.AspNetCore.Mvc.TagHelpers;
2+
using Microsoft.Extensions.Caching.Memory;
3+
using Our.Umbraco.TagHelpers.Services;
44
using Umbraco.Cms.Core.Events;
55
using Umbraco.Cms.Core.Notifications;
66

@@ -10,64 +10,44 @@ namespace Our.Umbraco.TagHelpers.Notifications
1010
// in the varyby key for the Cache TagHelper, to naively break the cache on publish.
1111
// Used for ContentCacheRefresher (Load balanced scernarios not just Published event)
1212
// Same for Dictionary Items and Media item
13-
public class HandleContentCacheRefresherNotification : INotificationHandler<ContentCacheRefresherNotification>
14-
{
15-
private readonly IAppPolicyCache _runtimeCache;
13+
public class CacheTagRefresherNotifications :
14+
INotificationHandler<ContentCacheRefresherNotification>,
15+
INotificationHandler<DictionaryCacheRefresherNotification>,
16+
INotificationHandler<MediaCacheRefresherNotification>
17+
{
1618

17-
public HandleContentCacheRefresherNotification(AppCaches appCaches)
18-
{
19-
_runtimeCache = appCaches.RuntimeCache;
20-
21-
}
22-
23-
public void Handle(ContentCacheRefresherNotification notification)
24-
{
25-
// fired when content published
26-
// store DateTime, as the cachekey
27-
var lastCacheRefreshDate = DateTime.UtcNow.ToString("s");
28-
29-
// insert and override existing value in appcache
30-
_runtimeCache.Insert(CacheKeyConstants.LastCacheRefreshDateKey, () => lastCacheRefreshDate, null, false, null);
31-
32-
}
33-
}
19+
private IMemoryCache _memoryCache;
3420

35-
public class HandleDictionaryCacheRefresherNotification : INotificationHandler<DictionaryCacheRefresherNotification>
36-
{
37-
private readonly IAppPolicyCache _runtimeCache;
38-
public HandleDictionaryCacheRefresherNotification(AppCaches appCaches)
39-
{
40-
_runtimeCache = appCaches.RuntimeCache;
41-
}
21+
private IUmbracoTagHelperCacheKeys _cacheKeys;
4222

43-
public void Handle(DictionaryCacheRefresherNotification notification)
23+
public CacheTagRefresherNotifications(CacheTagHelperMemoryCacheFactory cacheFactory, IUmbracoTagHelperCacheKeys cacheKeys)
4424
{
45-
// fired when Dictionary item updated
46-
// store DateTime, as the cachekey
47-
var lastCacheRefreshDate = DateTime.UtcNow.ToString("s");
25+
_memoryCache = cacheFactory.Cache;
26+
_cacheKeys = cacheKeys;
27+
}
4828

49-
// insert and override existing value in appcache
50-
_runtimeCache.Insert(CacheKeyConstants.LastCacheRefreshDateKey, () => lastCacheRefreshDate, null, false, null);
29+
public void Handle(ContentCacheRefresherNotification notification) => ClearUmbracoTagHelperCache();
5130

52-
}
53-
}
31+
public void Handle(DictionaryCacheRefresherNotification notification) => ClearUmbracoTagHelperCache();
5432

55-
public class HandleMediaCacheRefresherNotification : INotificationHandler<MediaCacheRefresherNotification>
56-
{
57-
private readonly IAppPolicyCache _runtimeCache;
58-
public HandleMediaCacheRefresherNotification(AppCaches appCaches)
59-
{
60-
_runtimeCache = appCaches.RuntimeCache;
61-
}
33+
public void Handle(MediaCacheRefresherNotification notification) => ClearUmbracoTagHelperCache();
6234

63-
public void Handle(MediaCacheRefresherNotification notification)
35+
private void ClearUmbracoTagHelperCache()
6436
{
65-
// fired when media updated
66-
// store DateTime, as the cachekey
67-
var lastCacheRefreshDate = DateTime.UtcNow.ToString("s");
68-
69-
// insert and override existing value in appcache
70-
_runtimeCache.Insert(CacheKeyConstants.LastCacheRefreshDateKey, () => lastCacheRefreshDate, null, false, null);
37+
// Loop over items in dictionary
38+
foreach (var item in _cacheKeys.CacheKeys)
39+
{
40+
// The value stores the CacheTagKey object
41+
// Looking at src code from MS TagHelper that use this object itself as the key
42+
43+
// Remove item from IMemoryCache
44+
_memoryCache.Remove(item.Value);
45+
}
46+
47+
// Once all items cleared from IMemoryCache that we are tracking
48+
// Clear the dictionary out
49+
// It will fill back up once an <our-cache> TagHelper is called/used on a page
50+
_cacheKeys.CacheKeys.Clear();
7151
}
7252
}
7353
}
Lines changed: 24 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
using Microsoft.AspNetCore.Mvc.TagHelpers;
2+
using Microsoft.AspNetCore.Mvc.TagHelpers.Cache;
23
using Microsoft.AspNetCore.Razor.TagHelpers;
3-
using Our.Umbraco.TagHelpers.CacheKeys;
4-
using System;
4+
using Our.Umbraco.TagHelpers.Services;
55
using System.Text.Encodings.Web;
66
using System.Threading.Tasks;
77
using Umbraco.Cms.Core;
8-
using Umbraco.Cms.Core.Cache;
98
using Umbraco.Cms.Core.Web;
10-
using Umbraco.Extensions;
119

1210
namespace Our.Umbraco.TagHelpers
1311
{
@@ -19,23 +17,21 @@ namespace Our.Umbraco.TagHelpers
1917
public class UmbracoCacheTagHelper : CacheTagHelper
2018
{
2119
private readonly IUmbracoContextFactory _umbracoContextFactory;
22-
private readonly IAppPolicyCache _runtimeCache;
23-
// default to true, a very 'Umbraco' convention.
24-
private bool _updateCacheKeyOnPublish = true;
20+
private readonly IUmbracoTagHelperCacheKeys _cacheKeys;
2521

2622
/// <summary>
2723
/// Whether to update the cache key when any content, media, dictionary item is published in Umbraco.
2824
/// </summary>
29-
public bool UpdateCacheKeyOnPublish
30-
{
31-
get { return _updateCacheKeyOnPublish; }
32-
set { _updateCacheKeyOnPublish = value; }
33-
}
25+
public bool UpdateCacheKeyOnPublish { get; set; } = true;
3426

35-
public UmbracoCacheTagHelper(CacheTagHelperMemoryCacheFactory factory, HtmlEncoder htmlEncoder, AppCaches appCaches, IUmbracoContextFactory umbracoContextFactory) : base(factory, htmlEncoder)
27+
public UmbracoCacheTagHelper(CacheTagHelperMemoryCacheFactory factory,
28+
HtmlEncoder htmlEncoder,
29+
IUmbracoContextFactory umbracoContextFactory,
30+
IUmbracoTagHelperCacheKeys cacheKeys)
31+
: base(factory, htmlEncoder)
3632
{
3733
_umbracoContextFactory = umbracoContextFactory;
38-
_runtimeCache = appCaches.RuntimeCache;
34+
_cacheKeys = cacheKeys;
3935
}
4036

4137
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
@@ -47,52 +43,28 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu
4743
// we don't want to enable the cache tag helper if Umbraco is in Preview, or in Debug mode
4844
if (umbracoContext.InPreviewMode || umbracoContext.IsDebug)
4945
{
46+
// Set the endabled flag to false & lest base class
47+
// of the cache tag helper do the same stuff as before
5048
this.Enabled = false;
51-
await output.GetChildContentAsync();
5249
}
5350
else
5451
{
55-
// with the CacheTagHelper we are wrapping here it's really difficult to clear the cache of the Tag Helper output 'on demand'
56-
// eg when a page is published, with Umbraco's CachedPartial that's what happens, so if you change the name of a page, and the site navigation
57-
// is cached with a cached partial, the cache is automatically cleared.
58-
// it seems for CacheTagHelper the .net core advice when you want to break the cache is to update the varyby key, the previous key in the cache will be forgotten
59-
// and fall out of the cache naturally... (but I did later on find this article: https://www.umbrajobs.com/blog/posts/2021/june/umbraco-9-net-core-caching-part-1-cashing-shared-partial-views/
60-
// where it talks about being able to clear the actual cache tag using reflection.. - it won't work on loadbalanced servers - using wrong notifications!
61-
// ... so maybe that's the way people will ultimately prefer to go...
62-
// but for this tag helper we just track the last time a peace of content, dictionary item or media was published, and use that datetime in the varyby cachekey
63-
// so everytime something is published in Umbraco, the cache tag helper will have a different cache key and this will produce a new cached result
64-
// this might be a bad thing?
65-
// so we have a setting to turn this off, so the tag helper is still usable as the existing .net core cache tag helper, without caching in preview or umbraco debug
66-
// which is still handyish
67-
if (_updateCacheKeyOnPublish)
52+
// Defaults to true - have to explicitly opt out with attribute set to false in Razor
53+
if (UpdateCacheKeyOnPublish)
6854
{
69-
// ironically read the last cache refresh date from runtime cache, and set it to now if it's not there...
70-
var umbLastCacheRefreshCacheKey = _runtimeCache.GetCacheItem(CacheKeyConstants.LastCacheRefreshDateKey, () => GetFallbackCacheRefreshDate()).ToString();
71-
72-
// append to VaryBy key incase VaryBy key is set to some other parameter too
73-
this.VaryBy = umbLastCacheRefreshCacheKey + "|" + this.VaryBy;
74-
75-
// if an expiry date isn't set when using the CacheTagHelper, let's add one to be 24hrs, so when mulitple publishes occur, the versions of this taghelper don't hang around forever
76-
if (this.ExpiresAfter == null)
77-
{
78-
this.ExpiresAfter = new TimeSpan(24, 0, 0);
79-
}
80-
}
55+
// So before we go into our base class
56+
// Grab the cache key so we can keep track of it & put into some dictionary or collection
57+
// and clear all items out in that collection with our notifications on publish
8158

82-
await base.ProcessAsync(context, output);
59+
var cacheKey = new CacheTagKey(this, context);
60+
var key = cacheKey.GenerateKey();
61+
var hashedKey = cacheKey.GenerateHashedKey();
62+
_cacheKeys.CacheKeys.TryAdd(key, cacheKey);
63+
}
8364
}
84-
}
8565

86-
}
87-
88-
private string GetFallbackCacheRefreshDate()
89-
{
90-
// this fires if the 'appcache' doesn't have a LastCacheRefreshDate set by a publish
91-
// eg after an app pool recycle
92-
// it doesn't really matter that this isn't the last datetime that something was actually published
93-
// because time tends to always move forwards
94-
// the next publish will set a new future LastCacheRefreshDate...
95-
return DateTime.UtcNow.ToString("s");
66+
await base.ProcessAsync(context, output);
67+
}
9668
}
9769
}
9870
}

0 commit comments

Comments
 (0)