diff --git a/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs index 5cbeb6cdbe97..3786951370e4 100644 --- a/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs @@ -4,6 +4,7 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Services.Changes; namespace Umbraco.Extensions; @@ -152,11 +153,49 @@ public static void RefreshContentCache(this DistributedCache dc, IEnumerable members) - => dc.RefreshByPayload(MemberCacheRefresher.UniqueId, members.DistinctBy(x => (x.Id, x.Username)).Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username, false))); + => dc.RefreshMemberCache(members, new Dictionary()); + public static void RefreshMemberCache(this DistributedCache dc, IEnumerable members, IDictionary state) + => dc.RefreshByPayload( + MemberCacheRefresher.UniqueId, + GetPayloads(members, state, false)); + + [Obsolete("Please use the overload taking all parameters. Scheduled for removal in Umbraco 18.")] public static void RemoveMemberCache(this DistributedCache dc, IEnumerable members) - => dc.RefreshByPayload(MemberCacheRefresher.UniqueId, members.DistinctBy(x => (x.Id, x.Username)).Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username, true))); + => dc.RemoveMemberCache(members, new Dictionary()); + + public static void RemoveMemberCache(this DistributedCache dc, IEnumerable members, IDictionary state) + => dc.RefreshByPayload( + MemberCacheRefresher.UniqueId, + GetPayloads(members, state, true)); + + // Internal for unit test. + internal static IEnumerable GetPayloads(IEnumerable members, IDictionary state, bool removed) + => members + .DistinctBy(x => (x.Id, x.Username)) + .Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username, removed) + { + PreviousUsername = GetPreviousUsername(x, state) + }); + + private static string? GetPreviousUsername(IMember x, IDictionary state) + { + if (state.TryGetValue(MemberSavedNotification.PreviousUsernameStateKey, out object? previousUserNames) is false) + { + return null; + } + + if (previousUserNames is not IDictionary previousUserNamesDictionary) + { + return null; + } + + return previousUserNamesDictionary.TryGetValue(x.Key, out string? previousUsername) + ? previousUsername + : null; + } #endregion diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/DistributedCacheNotificationHandlerBase.cs b/src/Umbraco.Core/Cache/NotificationHandlers/DistributedCacheNotificationHandlerBase.cs index f8feab19a661..bc35aeca2115 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/DistributedCacheNotificationHandlerBase.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/DistributedCacheNotificationHandlerBase.cs @@ -10,11 +10,20 @@ public abstract class DistributedCacheNotificationHandlerBase public void Handle(TNotification notification) - => Handle(GetEntities(notification)); + => Handle( + GetEntities(notification), + notification is StatefulNotification statefulNotification + ? statefulNotification.State + : new Dictionary()); /// public void Handle(IEnumerable notifications) - => Handle(notifications.SelectMany(GetEntities)); + { + foreach (TNotification notification in notifications) + { + Handle(notification); + } + } /// /// Gets the entities from the specified notification. @@ -25,9 +34,23 @@ public void Handle(IEnumerable notifications) /// protected abstract IEnumerable GetEntities(TNotification notification); + // TODO (V18): When removing the obsolete method, make the remaining Handle method abstract + // rather than virtual. It couldn't be made abstract when introduced as that would be a breaking change. + /// /// Handles the specified entities. /// /// The entities. + [Obsolete("Please use the overload taking all parameters. Scheduled for removal in Umbraco 18.")] protected abstract void Handle(IEnumerable entities); + + /// + /// Handles the specified entities. + /// + /// The entities. + /// The notification state. + protected virtual void Handle(IEnumerable entities, IDictionary state) +#pragma warning disable CS0618 // Type or member is obsolete + => Handle(entities); +#pragma warning restore CS0618 // Type or member is obsolete } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/ContentTreeChangeDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/ContentTreeChangeDistributedCacheNotificationHandler.cs index 8d8059afe173..527e27c576fa 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/ContentTreeChangeDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/ContentTreeChangeDistributedCacheNotificationHandler.cs @@ -18,6 +18,11 @@ public ContentTreeChangeDistributedCacheNotificationHandler(DistributedCache dis => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable> entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable> entities, IDictionary state) => _distributedCache.RefreshContentCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/ContentTypeChangedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/ContentTypeChangedDistributedCacheNotificationHandler.cs index bad629567448..54a7697732e0 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/ContentTypeChangedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/ContentTypeChangedDistributedCacheNotificationHandler.cs @@ -18,6 +18,11 @@ public ContentTypeChangedDistributedCacheNotificationHandler(DistributedCache di => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable> entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable> entities, IDictionary state) => _distributedCache.RefreshContentTypeCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DataTypeDeletedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DataTypeDeletedDistributedCacheNotificationHandler.cs index 251e34e27bb6..cfa9b80a22c8 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DataTypeDeletedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DataTypeDeletedDistributedCacheNotificationHandler.cs @@ -1,5 +1,6 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services.Changes; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache; @@ -17,7 +18,12 @@ public DataTypeDeletedDistributedCacheNotificationHandler(DistributedCache distr => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) { _distributedCache.RemoveDataTypeCache(entities); _distributedCache.RefreshValueEditorCache(entities); diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DataTypeSavedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DataTypeSavedDistributedCacheNotificationHandler.cs index 273eaf4e3fa5..60077c12884e 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DataTypeSavedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DataTypeSavedDistributedCacheNotificationHandler.cs @@ -17,7 +17,12 @@ public DataTypeSavedDistributedCacheNotificationHandler(DistributedCache distrib => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) { _distributedCache.RefreshDataTypeCache(entities); _distributedCache.RefreshValueEditorCache(entities); diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DictionaryItemDeletedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DictionaryItemDeletedDistributedCacheNotificationHandler.cs index 410d237cd7d7..162558b89b20 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DictionaryItemDeletedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DictionaryItemDeletedDistributedCacheNotificationHandler.cs @@ -17,6 +17,11 @@ public DictionaryItemDeletedDistributedCacheNotificationHandler(DistributedCache => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RemoveDictionaryCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DictionaryItemSavedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DictionaryItemSavedDistributedCacheNotificationHandler.cs index 6ce67545016a..ab032844c0f0 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DictionaryItemSavedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DictionaryItemSavedDistributedCacheNotificationHandler.cs @@ -17,6 +17,11 @@ public DictionaryItemSavedDistributedCacheNotificationHandler(DistributedCache d => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RefreshDictionaryCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DomainDeletedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DomainDeletedDistributedCacheNotificationHandler.cs index 8752dac3ccd9..2a8414e2be90 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DomainDeletedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DomainDeletedDistributedCacheNotificationHandler.cs @@ -17,6 +17,11 @@ public DomainDeletedDistributedCacheNotificationHandler(DistributedCache distrib => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RemoveDomainCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DomainSavedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DomainSavedDistributedCacheNotificationHandler.cs index d83695190067..db21e2e6deff 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DomainSavedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/DomainSavedDistributedCacheNotificationHandler.cs @@ -17,6 +17,11 @@ public DomainSavedDistributedCacheNotificationHandler(DistributedCache distribut => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RefreshDomainCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/LanguageDeletedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/LanguageDeletedDistributedCacheNotificationHandler.cs index 81b944eb468a..c89c88e8bf7c 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/LanguageDeletedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/LanguageDeletedDistributedCacheNotificationHandler.cs @@ -17,6 +17,11 @@ public LanguageDeletedDistributedCacheNotificationHandler(DistributedCache distr => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RemoveLanguageCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/LanguageSavedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/LanguageSavedDistributedCacheNotificationHandler.cs index c3685161720d..7715bdfdffe0 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/LanguageSavedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/LanguageSavedDistributedCacheNotificationHandler.cs @@ -17,6 +17,11 @@ public LanguageSavedDistributedCacheNotificationHandler(DistributedCache distrib => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RefreshLanguageCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MediaTreeChangeDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MediaTreeChangeDistributedCacheNotificationHandler.cs index 19ea514e7296..fc308d173bd1 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MediaTreeChangeDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MediaTreeChangeDistributedCacheNotificationHandler.cs @@ -18,6 +18,11 @@ public MediaTreeChangeDistributedCacheNotificationHandler(DistributedCache distr => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable> entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable> entities, IDictionary state) => _distributedCache.RefreshMediaCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MediaTypeChangedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MediaTypeChangedDistributedCacheNotificationHandler.cs index 9952ac2b872c..1edfe6b46cc7 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MediaTypeChangedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MediaTypeChangedDistributedCacheNotificationHandler.cs @@ -18,6 +18,11 @@ public MediaTypeChangedDistributedCacheNotificationHandler(DistributedCache dist => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable> entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable> entities, IDictionary state) => _distributedCache.RefreshContentTypeCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberDeletedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberDeletedDistributedCacheNotificationHandler.cs index d72bd91812c7..33ca8419e2dc 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberDeletedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberDeletedDistributedCacheNotificationHandler.cs @@ -1,5 +1,6 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services.Changes; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache; @@ -17,6 +18,11 @@ public MemberDeletedDistributedCacheNotificationHandler(DistributedCache distrib => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) - => _distributedCache.RemoveMemberCache(entities); + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) + => _distributedCache.RemoveMemberCache(entities, state); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberGroupDeletedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberGroupDeletedDistributedCacheNotificationHandler.cs index fe352c2e2a29..0cfe7d871bbf 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberGroupDeletedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberGroupDeletedDistributedCacheNotificationHandler.cs @@ -17,6 +17,11 @@ public MemberGroupDeletedDistributedCacheNotificationHandler(DistributedCache di => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RemoveMemberGroupCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberGroupSavedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberGroupSavedDistributedCacheNotificationHandler.cs index 34cb344cfb3c..df2c1e1b0280 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberGroupSavedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberGroupSavedDistributedCacheNotificationHandler.cs @@ -1,4 +1,4 @@ -using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; using Umbraco.Extensions; @@ -17,6 +17,11 @@ public MemberGroupSavedDistributedCacheNotificationHandler(DistributedCache dist => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RefreshMemberGroupCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberSavedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberSavedDistributedCacheNotificationHandler.cs index 97aad2b1aeca..9158c9d2c1ec 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberSavedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberSavedDistributedCacheNotificationHandler.cs @@ -17,6 +17,11 @@ public MemberSavedDistributedCacheNotificationHandler(DistributedCache distribut => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) - => _distributedCache.RefreshMemberCache(entities); + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) + => _distributedCache.RefreshMemberCache(entities, state); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberTypeChangedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberTypeChangedDistributedCacheNotificationHandler.cs index 77727d5ceb07..2fb283eff3ce 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberTypeChangedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/MemberTypeChangedDistributedCacheNotificationHandler.cs @@ -19,6 +19,11 @@ public MemberTypeChangedDistributedCacheNotificationHandler(DistributedCache dis => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable> entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable> entities, IDictionary state) => _distributedCache.RefreshContentTypeCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/PublicAccessEntryDeletedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/PublicAccessEntryDeletedDistributedCacheNotificationHandler.cs index ea85ab1c6726..49ace53a1288 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/PublicAccessEntryDeletedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/PublicAccessEntryDeletedDistributedCacheNotificationHandler.cs @@ -1,5 +1,6 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services.Changes; using Umbraco.Extensions; namespace Umbraco.Cms.Core.Cache; @@ -17,6 +18,11 @@ public PublicAccessEntryDeletedDistributedCacheNotificationHandler(DistributedCa => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RefreshPublicAccess(); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/PublicAccessEntrySavedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/PublicAccessEntrySavedDistributedCacheNotificationHandler.cs index e04ed160ecd1..141a4101b729 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/PublicAccessEntrySavedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/PublicAccessEntrySavedDistributedCacheNotificationHandler.cs @@ -17,6 +17,11 @@ public PublicAccessEntrySavedDistributedCacheNotificationHandler(DistributedCach => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RefreshPublicAccess(); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/RelationTypeDeletedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/RelationTypeDeletedDistributedCacheNotificationHandler.cs index 25ec41d10dec..c4ca7ad4479c 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/RelationTypeDeletedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/RelationTypeDeletedDistributedCacheNotificationHandler.cs @@ -17,6 +17,11 @@ public RelationTypeDeletedDistributedCacheNotificationHandler(DistributedCache d => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RemoveRelationTypeCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/RelationTypeSavedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/RelationTypeSavedDistributedCacheNotificationHandler.cs index 096af3ee2f6c..2b446b36199b 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/RelationTypeSavedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/RelationTypeSavedDistributedCacheNotificationHandler.cs @@ -17,6 +17,11 @@ public RelationTypeSavedDistributedCacheNotificationHandler(DistributedCache dis => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RefreshRelationTypeCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/TemplateDeletedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/TemplateDeletedDistributedCacheNotificationHandler.cs index 99ca54f4e27a..04af833beb26 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/TemplateDeletedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/TemplateDeletedDistributedCacheNotificationHandler.cs @@ -17,6 +17,11 @@ public TemplateDeletedDistributedCacheNotificationHandler(DistributedCache distr => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RemoveTemplateCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/TemplateSavedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/TemplateSavedDistributedCacheNotificationHandler.cs index aba5624ba660..d707dde1431d 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/TemplateSavedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/TemplateSavedDistributedCacheNotificationHandler.cs @@ -17,6 +17,11 @@ public TemplateSavedDistributedCacheNotificationHandler(DistributedCache distrib => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RefreshTemplateCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserDeletedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserDeletedDistributedCacheNotificationHandler.cs index 78e14d7d2f97..87debe4808be 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserDeletedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserDeletedDistributedCacheNotificationHandler.cs @@ -1,3 +1,4 @@ +using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; using Umbraco.Cms.Core.Notifications; using Umbraco.Extensions; @@ -17,6 +18,11 @@ public UserDeletedDistributedCacheNotificationHandler(DistributedCache distribut => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RemoveUserCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserGroupDeletedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserGroupDeletedDistributedCacheNotificationHandler.cs index 17c1b071d675..5e6ffcef29f9 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserGroupDeletedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserGroupDeletedDistributedCacheNotificationHandler.cs @@ -17,6 +17,11 @@ public UserGroupDeletedDistributedCacheNotificationHandler(DistributedCache dist => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RemoveUserGroupCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserGroupWithUsersSavedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserGroupWithUsersSavedDistributedCacheNotificationHandler.cs index d9e2fa956403..6e931de64224 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserGroupWithUsersSavedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserGroupWithUsersSavedDistributedCacheNotificationHandler.cs @@ -22,6 +22,11 @@ protected override IEnumerable GetEntities(UserGroupWithUsersSavedNo => notification.SavedEntities.Select(x => x.UserGroup); /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RefreshUserGroupCache(entities); } diff --git a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserSavedDistributedCacheNotificationHandler.cs b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserSavedDistributedCacheNotificationHandler.cs index 23da343bcd1b..41f9daadc871 100644 --- a/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserSavedDistributedCacheNotificationHandler.cs +++ b/src/Umbraco.Core/Cache/NotificationHandlers/Implement/UserSavedDistributedCacheNotificationHandler.cs @@ -17,6 +17,11 @@ public UserSavedDistributedCacheNotificationHandler(DistributedCache distributed => _distributedCache = distributedCache; /// + [Obsolete("Scheduled for removal in Umbraco 18.")] protected override void Handle(IEnumerable entities) + => Handle(entities, new Dictionary()); + + /// + protected override void Handle(IEnumerable entities, IDictionary state) => _distributedCache.RefreshUserCache(entities); } diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs index 3e4181feac2a..de38b25d3255 100644 --- a/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/Refreshers/Implement/MemberCacheRefresher.cs @@ -62,6 +62,8 @@ public JsonPayload(int id, string? username, bool removed) public string? Username { get; } + public string? PreviousUsername { get; set; } + public bool Removed { get; } } @@ -112,6 +114,13 @@ private void ClearCache(params JsonPayload[] payloads) // https://github.com/umbraco/Umbraco-CMS/pull/17350 // https://github.com/umbraco/Umbraco-CMS/pull/17815 memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(CacheKeys.MemberUserNameCachePrefix + p.Username)); + + // If provided, clear the cache by the previous user name too. + if (string.IsNullOrEmpty(p.PreviousUsername) is false) + { + memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(p.PreviousUsername)); + memberCache.Result?.Clear(RepositoryCacheKeys.GetKey(CacheKeys.MemberUserNameCachePrefix + p.PreviousUsername)); + } } } } diff --git a/src/Umbraco.Core/Notifications/MemberSavedNotification.cs b/src/Umbraco.Core/Notifications/MemberSavedNotification.cs index 9bc6097ca26b..d51db13a5064 100644 --- a/src/Umbraco.Core/Notifications/MemberSavedNotification.cs +++ b/src/Umbraco.Core/Notifications/MemberSavedNotification.cs @@ -10,6 +10,14 @@ namespace Umbraco.Cms.Core.Notifications; /// public sealed class MemberSavedNotification : SavedNotification { + /// + /// Defines the notification state key for tracking the previous username of a saved member. + /// + internal const string PreviousUsernameStateKey = "PreviousUsername"; + + /// + /// Initializes a new instance of the class. + /// public MemberSavedNotification(IMember target, EventMessages messages) : base(target, messages) { diff --git a/src/Umbraco.Core/Services/MemberService.cs b/src/Umbraco.Core/Services/MemberService.cs index c2499a9efe78..e88881be42c9 100644 --- a/src/Umbraco.Core/Services/MemberService.cs +++ b/src/Umbraco.Core/Services/MemberService.cs @@ -795,16 +795,29 @@ public bool Exists(string username) throw new ArgumentException("Cannot save member with empty name."); } + var previousUsername = _memberRepository.Get(member.Id)?.Username; + scope.WriteLock(Constants.Locks.MemberTree); _memberRepository.Save(member); if (publishNotificationSaveOptions.HasFlag(PublishNotificationSaveOptions.Saved)) { - scope.Notifications.Publish( - savingNotification is null + MemberSavedNotification memberSavedNotification = savingNotification is null ? new MemberSavedNotification(member, evtMsgs) - : new MemberSavedNotification(member, evtMsgs).WithStateFrom(savingNotification)); + : new MemberSavedNotification(member, evtMsgs).WithStateFrom(savingNotification); + + // If the user name has changed, populate the previous user name in the notification state, so the cache refreshers + // have it available to clear the cache by the old name as well as the new. + if (string.IsNullOrWhiteSpace(previousUsername) is false && + string.Equals(previousUsername, member.Username, StringComparison.OrdinalIgnoreCase) is false) + { + memberSavedNotification.State.Add( + MemberSavedNotification.PreviousUsernameStateKey, + new Dictionary { { member.Key, previousUsername } }); + } + + scope.Notifications.Publish(memberSavedNotification); } Audit(AuditType.Save, userId, member.Id); diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCacheExtensionsTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCacheExtensionsTests.cs new file mode 100644 index 000000000000..fed1fe23b952 --- /dev/null +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/DistributedCacheExtensionsTests.cs @@ -0,0 +1,71 @@ +// Copyright (c) Umbraco. +// See LICENSE for more details. + +using NUnit.Framework; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Core.Cache; + +[TestFixture] +public class DistributedCacheExtensionsTests +{ + [TestCase(true)] + [TestCase(false)] + public void Member_GetPayloads_CorrectlyCreatesPayloads(bool removed) + { + var member1Key = Guid.NewGuid(); + var member2Key = Guid.NewGuid(); + var member3Key = Guid.NewGuid(); + var members = new List() + { + CreateMember(1, member1Key, "Fred", "fred", "fred@test.com"), + CreateMember(1, member1Key, "Fred", "fred", "fred@test.com"), + CreateMember(2, member2Key, "Sally", "sally", "sally@test.com"), + CreateMember(3, member3Key, "Jane", "jane", "jane@test.com"), + }; + + var state = new Dictionary + { + { + MemberSavedNotification.PreviousUsernameStateKey, + new Dictionary { { member3Key, "janeold" } } + }, + }; + + var payloads = DistributedCacheExtensions.GetPayloads(members, state, removed); + Assert.AreEqual(3, payloads.Count()); + + var payloadForFred = payloads.First(); + Assert.AreEqual("fred", payloadForFred.Username); + Assert.AreEqual(1, payloadForFred.Id); + Assert.IsNull(payloadForFred.PreviousUsername); + Assert.AreEqual(removed, payloadForFred.Removed); + + var payloadForSally = payloads.Skip(1).First(); + Assert.AreEqual("sally", payloadForSally.Username); + Assert.AreEqual(2, payloadForSally.Id); + Assert.IsNull(payloadForSally.PreviousUsername); + Assert.AreEqual(removed, payloadForSally.Removed); + + var payloadForJane = payloads.Skip(2).First(); + Assert.AreEqual("jane", payloadForJane.Username); + Assert.AreEqual(3, payloadForJane.Id); + Assert.AreEqual("janeold", payloadForJane.PreviousUsername); + Assert.AreEqual(removed, payloadForJane.Removed); + } + + private static IMember CreateMember(int id, Guid key, string name, string username, string email) + => new MemberBuilder() + .AddMemberType() + .Done() + .WithId(id) + .WithKey(key) + .WithName(name) + .WithLogin(username, "password") + .WithEmail(email) + .Build(); +}