diff --git a/src/Umbraco.Core/Cache/PartialViewCacheInvalidators/IMemberPartialViewCacheInvalidator.cs b/src/Umbraco.Core/Cache/PartialViewCacheInvalidators/IMemberPartialViewCacheInvalidator.cs
new file mode 100644
index 000000000000..289dad528844
--- /dev/null
+++ b/src/Umbraco.Core/Cache/PartialViewCacheInvalidators/IMemberPartialViewCacheInvalidator.cs
@@ -0,0 +1,16 @@
+namespace Umbraco.Cms.Core.Cache.PartialViewCacheInvalidators;
+
+///
+/// Defines behaviours for clearing of cached partials views that are configured to be cached individually by member.
+///
+public interface IMemberPartialViewCacheInvalidator
+{
+ ///
+ /// Clears the partial view cache items for the specified member ids.
+ ///
+ /// The member Ids to clear the cache for.
+ ///
+ /// Called from the when a member is saved or deleted.
+ ///
+ void ClearPartialViewCacheItems(IEnumerable memberIds);
+}
diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs
index 1c19f62576f1..3e4181feac2a 100644
--- a/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs
+++ b/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs
@@ -1,10 +1,12 @@
+using Microsoft.Extensions.DependencyInjection;
+using Umbraco.Cms.Core.Cache.PartialViewCacheInvalidators;
+using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Notifications;
using Umbraco.Cms.Core.Persistence.Repositories;
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
-using Umbraco.Extensions;
namespace Umbraco.Cms.Core.Cache;
@@ -13,10 +15,32 @@ public sealed class MemberCacheRefresher : PayloadCacheRefresherBase
+ : this(
+ appCaches,
+ serializer,
+ idKeyMap,
+ eventAggregator,
+ factory,
+ StaticServiceProvider.Instance.GetRequiredService())
+ {
+ }
+
+ public MemberCacheRefresher(
+ AppCaches appCaches,
+ IJsonSerializer serializer,
+ IIdKeyMap idKeyMap,
+ IEventAggregator eventAggregator,
+ ICacheRefresherNotificationFactory factory,
+ IMemberPartialViewCacheInvalidator memberPartialViewCacheInvalidator)
+ : base(appCaches, serializer, eventAggregator, factory)
+ {
_idKeyMap = idKeyMap;
+ _memberPartialViewCacheInvalidator = memberPartialViewCacheInvalidator;
+ }
#region Indirect
@@ -65,7 +89,8 @@ public override void Remove(int id)
private void ClearCache(params JsonPayload[] payloads)
{
- AppCaches.ClearPartialViewCache();
+ _memberPartialViewCacheInvalidator.ClearPartialViewCacheItems(payloads.Select(p => p.Id));
+
Attempt memberCache = AppCaches.IsolatedCaches.Get();
foreach (JsonPayload p in payloads)
diff --git a/src/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidator.cs b/src/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidator.cs
new file mode 100644
index 000000000000..8ee4be4c00ee
--- /dev/null
+++ b/src/Umbraco.Web.Website/Cache/PartialViewCacheInvalidators/MemberPartialViewCacheInvalidator.cs
@@ -0,0 +1,38 @@
+using Umbraco.Cms.Core.Cache;
+using Umbraco.Cms.Core.Cache.PartialViewCacheInvalidators;
+using Umbraco.Extensions;
+
+namespace Umbraco.Cms.Web.Website.Cache.PartialViewCacheInvalidators;
+
+///
+/// Implementation of that only remove cached partial views
+/// that were cached for the specified member(s).
+///
+public class MemberPartialViewCacheInvalidator : IMemberPartialViewCacheInvalidator
+{
+ private readonly AppCaches _appCaches;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MemberPartialViewCacheInvalidator(AppCaches appCaches) => _appCaches = appCaches;
+
+ ///
+ ///
+ /// Partial view cache keys follow the following format:
+ /// [] is optional or only added if the information is available
+ /// {} is a parameter
+ /// "Umbraco.Web.PartialViewCacheKey{partialViewName}-[{currentThreadCultureName}-][m{memberId}-][c{contextualKey}-]"
+ /// See for more information.
+ ///
+ public void ClearPartialViewCacheItems(IEnumerable memberIds)
+ {
+ foreach (var memberId in memberIds)
+ {
+ _appCaches.RuntimeCache.ClearByRegex($"{CoreCacheHelperExtensions.PartialViewCacheKey}.*-m{memberId}-*");
+ }
+
+ // since it is possible to add a cache item linked to members without a member logged in, we should always clear these items.
+ _appCaches.RuntimeCache.ClearByRegex($"{CoreCacheHelperExtensions.PartialViewCacheKey}.*-m-*");
+ }
+}
diff --git a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs
index 400b288786f3..2fd979d8ca92 100644
--- a/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs
+++ b/src/Umbraco.Web.Website/DependencyInjection/UmbracoBuilderExtensions.cs
@@ -5,6 +5,7 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using Umbraco.Cms.Core.Cache.PartialViewCacheInvalidators;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Routing;
@@ -13,6 +14,7 @@
using Umbraco.Cms.Infrastructure.DependencyInjection;
using Umbraco.Cms.Web.Common.Middleware;
using Umbraco.Cms.Web.Common.Routing;
+using Umbraco.Cms.Web.Website.Cache.PartialViewCacheInvalidators;
using Umbraco.Cms.Web.Website.Collections;
using Umbraco.Cms.Web.Website.Models;
using Umbraco.Cms.Web.Website.Routing;
@@ -73,6 +75,9 @@ public static IUmbracoBuilder AddWebsite(this IUmbracoBuilder builder)
builder.Services.AddSingleton();
builder.Services.AddSingleton();
+ // Partial view cache invalidators
+ builder.Services.AddUnique();
+
builder
.AddDistributedCache()
.AddModelsBuilder();
diff --git a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs
index 6892ffd1452f..efb48d59da8a 100644
--- a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs
+++ b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs
@@ -105,7 +105,45 @@ public static IHtmlContent PreviewBadge(
ViewDataDictionary? viewData = null,
Func