diff --git a/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs index 8d792a5ef7..438c66a2c1 100644 --- a/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Core/Cache/DistributedCacheExtensions.cs @@ -118,15 +118,35 @@ public static class DistributedCacheExtensions #region ContentCacheRefresher public static void RefreshAllContentCache(this DistributedCache dc) + { + ContentCacheRefresher.JsonPayload[] payloads = new[] + { + new ContentCacheRefresher.JsonPayload() + { + ChangeTypes = TreeChangeTypes.RefreshAll + } + }; + // note: refresh all content cache does refresh content types too - => dc.RefreshByPayload(ContentCacheRefresher.UniqueId, new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll).Yield()); + dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads); + } [Obsolete("Use the overload accepting IEnumerable instead to avoid allocating arrays. This overload will be removed in Umbraco 13.")] public static void RefreshContentCache(this DistributedCache dc, TreeChange[] changes) => dc.RefreshContentCache(changes.AsEnumerable()); public static void RefreshContentCache(this DistributedCache dc, IEnumerable> changes) - => dc.RefreshByPayload(ContentCacheRefresher.UniqueId, changes.DistinctBy(x => (x.Item.Id, x.Item.Key, x.ChangeTypes)).Select(x => new ContentCacheRefresher.JsonPayload(x.Item.Id, x.Item.Key, x.ChangeTypes))); + { + IEnumerable payloads = changes.Select(x => new ContentCacheRefresher.JsonPayload() + { + Id = x.Item.Id, + Key = x.Item.Key, + ChangeTypes = x.ChangeTypes, + Blueprint = x.Item.Blueprint + }); + + dc.RefreshByPayload(ContentCacheRefresher.UniqueId, payloads); + } #endregion diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs index a515d5c5d1..779b22fe68 100644 --- a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs @@ -84,8 +84,8 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase((k, v) => v.Path?.Contains(pathid) ?? false); } - // if the item is being completely removed, we need to refresh the domains cache if any domain was assigned to the content - if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.Remove)) + // if the item is not a blueprint and is being completely removed, we need to refresh the domains cache if any domain was assigned to the content + if (payload.Blueprint is false && payload.ChangeTypes.HasTypesAny(TreeChangeTypes.Remove)) { idsRemoved.Add(payload.Id); } @@ -120,7 +120,11 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase x.Blueprint is false)) + { + // Only notify if the payload contains actual (non-blueprint) contents + NotifyPublishedSnapshotService(_publishedSnapshotService, AppCaches, payloads); + } base.Refresh(payloads); } @@ -157,8 +161,13 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase(); - } - + deleteBatch ??= new HashSet(); deleteBatch.Add(payload.Id); } else if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) diff --git a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs index df8795eee6..2daca04602 100644 --- a/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs @@ -217,7 +217,16 @@ internal class PublishedSnapshotService : IPublishedSnapshotService // they require. using (_contentStore.GetScopedWriteLock(_scopeProvider)) { - NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _); + NotifyLocked( + new[] + { + new ContentCacheRefresher.JsonPayload() + { + ChangeTypes = TreeChangeTypes.RefreshAll + } + }, + out _, + out _); } using (_mediaStore.GetScopedWriteLock(_scopeProvider)) @@ -882,6 +891,12 @@ internal class PublishedSnapshotService : IPublishedSnapshotService _logger.LogDebug("Notified {ChangeTypes} for content {ContentId}", payload.ChangeTypes, payload.Id); } + if (payload.Blueprint) + { + // Skip blueprints + continue; + } + if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) { using (IScope scope = _scopeProvider.CreateScope()) diff --git a/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs b/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs index f7be9d129a..c75bbd5a80 100644 --- a/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs +++ b/src/Umbraco.Web.BackOffice/Filters/ContentSaveValidationAttribute.cs @@ -18,7 +18,7 @@ namespace Umbraco.Cms.Web.BackOffice.Filters; /// Validates the incoming model along with if the user is allowed to perform the /// operation /// -internal sealed class ContentSaveValidationAttribute : TypeFilterAttribute +public sealed class ContentSaveValidationAttribute : TypeFilterAttribute { public ContentSaveValidationAttribute(bool skipUserAccessValidation = false) : base(typeof(ContentSaveValidationFilter)) diff --git a/src/Umbraco.Web.BackOffice/ModelBinders/BlueprintItemBinder.cs b/src/Umbraco.Web.BackOffice/ModelBinders/BlueprintItemBinder.cs index bc07497fcd..bf8c7372bc 100644 --- a/src/Umbraco.Web.BackOffice/ModelBinders/BlueprintItemBinder.cs +++ b/src/Umbraco.Web.BackOffice/ModelBinders/BlueprintItemBinder.cs @@ -7,7 +7,7 @@ using Umbraco.Cms.Core.Services; namespace Umbraco.Cms.Web.BackOffice.ModelBinders; -internal class BlueprintItemBinder : ContentItemBinder +public class BlueprintItemBinder : ContentItemBinder { private readonly IContentService _contentService; diff --git a/src/Umbraco.Web.BackOffice/ModelBinders/ContentItemBinder.cs b/src/Umbraco.Web.BackOffice/ModelBinders/ContentItemBinder.cs index 0842ca2051..c73a45f904 100644 --- a/src/Umbraco.Web.BackOffice/ModelBinders/ContentItemBinder.cs +++ b/src/Umbraco.Web.BackOffice/ModelBinders/ContentItemBinder.cs @@ -13,7 +13,7 @@ namespace Umbraco.Cms.Web.BackOffice.ModelBinders; /// /// The model binder for /// -internal class ContentItemBinder : IModelBinder +public class ContentItemBinder : IModelBinder { private readonly IContentService _contentService; private readonly IContentTypeService _contentTypeService; diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RefresherTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RefresherTests.cs index 92ef2b5c9f..e509742cb9 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RefresherTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Core/Cache/RefresherTests.cs @@ -18,8 +18,10 @@ public class RefresherTests { new MediaCacheRefresher.JsonPayload(1234, Guid.NewGuid(), TreeChangeTypes.None), }; + var json = JsonConvert.SerializeObject(source); var payload = JsonConvert.DeserializeObject(json); + Assert.AreEqual(source[0].Id, payload[0].Id); Assert.AreEqual(source[0].Key, payload[0].Key); Assert.AreEqual(source[0].ChangeTypes, payload[0].ChangeTypes); @@ -30,13 +32,21 @@ public class RefresherTests { ContentCacheRefresher.JsonPayload[] source = { - new ContentCacheRefresher.JsonPayload(1234, Guid.NewGuid(), TreeChangeTypes.None), + new ContentCacheRefresher.JsonPayload() + { + Id = 1234, + Key = Guid.NewGuid(), + ChangeTypes = TreeChangeTypes.None + } }; + var json = JsonConvert.SerializeObject(source); var payload = JsonConvert.DeserializeObject(json); + Assert.AreEqual(source[0].Id, payload[0].Id); Assert.AreEqual(source[0].Key, payload[0].Key); Assert.AreEqual(source[0].ChangeTypes, payload[0].ChangeTypes); + Assert.AreEqual(source[0].Blueprint, payload[0].Blueprint); } [Test] @@ -46,8 +56,10 @@ public class RefresherTests { new ContentTypeCacheRefresher.JsonPayload("xxx", 1234, ContentTypeChangeTypes.None), }; + var json = JsonConvert.SerializeObject(source); var payload = JsonConvert.DeserializeObject(json); + Assert.AreEqual(source[0].ItemType, payload[0].ItemType); Assert.AreEqual(source[0].Id, payload[0].Id); Assert.AreEqual(source[0].ChangeTypes, payload[0].ChangeTypes); @@ -60,8 +72,10 @@ public class RefresherTests { new DataTypeCacheRefresher.JsonPayload(1234, Guid.NewGuid(), true), }; + var json = JsonConvert.SerializeObject(source); var payload = JsonConvert.DeserializeObject(json); + Assert.AreEqual(source[0].Id, payload[0].Id); Assert.AreEqual(source[0].Key, payload[0].Key); Assert.AreEqual(source[0].Removed, payload[0].Removed); @@ -70,10 +84,14 @@ public class RefresherTests [Test] public void DomainCacheRefresherCanDeserializeJsonPayload() { - DomainCacheRefresher.JsonPayload[] - source = { new DomainCacheRefresher.JsonPayload(1234, DomainChangeTypes.None) }; + DomainCacheRefresher.JsonPayload[] source = + { + new DomainCacheRefresher.JsonPayload(1234, DomainChangeTypes.None) + }; + var json = JsonConvert.SerializeObject(source); var payload = JsonConvert.DeserializeObject(json); + Assert.AreEqual(source[0].Id, payload[0].Id); Assert.AreEqual(source[0].ChangeType, payload[0].ChangeType); } diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedSnapshotServiceCollectionTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedSnapshotServiceCollectionTests.cs index a007079ca9..b516b4fffb 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedSnapshotServiceCollectionTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/PublishedCache/PublishedSnapshotServiceCollectionTests.cs @@ -358,7 +358,16 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT // notify SnapshotService.Notify( - new[] { new ContentCacheRefresher.JsonPayload(10, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _); + new[] + { + new ContentCacheRefresher.JsonPayload() + { + Id = 10, + ChangeTypes = TreeChangeTypes.RefreshBranch + } + }, + out _, + out _); // changes that *I* make are immediately visible on the current snapshot var documents = snapshot.Content.GetAtRoot().ToArray(); @@ -392,7 +401,16 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT // notify SnapshotService.Notify( - new[] { new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _); + new[] + { + new ContentCacheRefresher.JsonPayload() + { + Id = 1, + ChangeTypes = TreeChangeTypes.RefreshBranch + } + }, + out _, + out _); // changes that *I* make are immediately visible on the current snapshot var documents = snapshot.Content.GetAtRoot().ToArray(); @@ -450,7 +468,11 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT SnapshotService.Notify( new[] { - new ContentCacheRefresher.JsonPayload(kit.Node.ParentContentId, Guid.Empty, TreeChangeTypes.RefreshBranch), + new ContentCacheRefresher.JsonPayload() + { + Id = kit.Node.ParentContentId, + ChangeTypes = TreeChangeTypes.RefreshBranch + } }, out _, out _); @@ -516,11 +538,19 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT // notify SnapshotService.Notify( new[] - { - // removal must come first - new ContentCacheRefresher.JsonPayload(2, Guid.Empty, TreeChangeTypes.RefreshBranch), - new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshBranch), - }, + { + // removal must come first + new ContentCacheRefresher.JsonPayload() + { + Id = 2, + ChangeTypes = TreeChangeTypes.RefreshBranch + }, + new ContentCacheRefresher.JsonPayload() + { + Id = 1, + ChangeTypes = TreeChangeTypes.RefreshBranch + } + }, out _, out _); @@ -571,7 +601,16 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT // notify - which ensures there are 2 generations in the cache meaning each LinkedNode has a Next value. SnapshotService.Notify( - new[] { new ContentCacheRefresher.JsonPayload(4, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _); + new[] + { + new ContentCacheRefresher.JsonPayload() + { + Id = 4, + ChangeTypes = TreeChangeTypes.RefreshBranch + } + }, + out _, + out _); // refresh the branch again, this used to show the issue where a null ref exception would occur // because in the ClearBranchLocked logic, when SetValueLocked was called within a recursive call @@ -579,7 +618,16 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT // this value before recursing. Assert.DoesNotThrow(() => SnapshotService.Notify( - new[] { new ContentCacheRefresher.JsonPayload(4, Guid.Empty, TreeChangeTypes.RefreshBranch) }, out _, out _)); + new[] + { + new ContentCacheRefresher.JsonPayload() + { + Id = 4, + ChangeTypes = TreeChangeTypes.RefreshBranch + } + }, + out _, + out _)); } [Test] @@ -759,11 +807,23 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT // notify SnapshotService.Notify( new[] - { - new ContentCacheRefresher.JsonPayload(3, Guid.Empty, TreeChangeTypes.Remove), // remove last - new ContentCacheRefresher.JsonPayload(5, Guid.Empty, TreeChangeTypes.Remove), // remove middle - new ContentCacheRefresher.JsonPayload(9, Guid.Empty, TreeChangeTypes.Remove), // remove first - }, + { + new ContentCacheRefresher.JsonPayload() // remove last + { + Id = 3, + ChangeTypes = TreeChangeTypes.Remove + }, + new ContentCacheRefresher.JsonPayload() // remove middle + { + Id = 5, + ChangeTypes = TreeChangeTypes.Remove + }, + new ContentCacheRefresher.JsonPayload() // remove first + { + Id = 9, + ChangeTypes = TreeChangeTypes.Remove + } + }, out _, out _); @@ -779,11 +839,23 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT // notify SnapshotService.Notify( new[] - { - new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.Remove), // remove first - new ContentCacheRefresher.JsonPayload(8, Guid.Empty, TreeChangeTypes.Remove), // remove - new ContentCacheRefresher.JsonPayload(7, Guid.Empty, TreeChangeTypes.Remove), // remove - }, + { + new ContentCacheRefresher.JsonPayload() // remove first + { + Id = 1, + ChangeTypes = TreeChangeTypes.Remove + }, + new ContentCacheRefresher.JsonPayload() // remove + { + Id = 8, + ChangeTypes = TreeChangeTypes.Remove + }, + new ContentCacheRefresher.JsonPayload() // remove + { + Id = 7, + ChangeTypes = TreeChangeTypes.Remove + } + }, out _, out _); @@ -823,8 +895,16 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT SnapshotService.Notify( new[] { - new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshBranch), - new ContentCacheRefresher.JsonPayload(2, Guid.Empty, TreeChangeTypes.RefreshNode), + new ContentCacheRefresher.JsonPayload() + { + Id = 1, + ChangeTypes = TreeChangeTypes.RefreshBranch + }, + new ContentCacheRefresher.JsonPayload() + { + Id = 2, + ChangeTypes = TreeChangeTypes.RefreshNode + } }, out _, out _); @@ -887,7 +967,17 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT var parentNode = parentNodes[0]; AssertLinkedNode(parentNode.contentNode, -1, -1, -1, 2, 2); - SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(2, Guid.Empty, TreeChangeTypes.Remove) }, out _, out _); + SnapshotService.Notify( + new[] + { + new ContentCacheRefresher.JsonPayload() + { + Id = 2, + ChangeTypes = TreeChangeTypes.Remove + } + }, + out _, + out _); parentNodes = contentStore.Test.GetValues(1); parentNode = parentNodes[0]; @@ -944,9 +1034,13 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT SnapshotService.Notify( new[] - { - new ContentCacheRefresher.JsonPayload(3, Guid.Empty, TreeChangeTypes.Remove), // remove middle child - }, + { + new ContentCacheRefresher.JsonPayload() // remove middle child + { + Id = 3, + ChangeTypes = TreeChangeTypes.Remove + } + }, out _, out _); @@ -1013,7 +1107,16 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT Assert.IsFalse(contentStore.Test.NextGen); SnapshotService.Notify( - new[] { new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshNode) }, out _, out _); + new[] + { + new ContentCacheRefresher.JsonPayload() + { + Id = 1, + ChangeTypes = TreeChangeTypes.RefreshNode + } + }, + out _, + out _); Assert.AreEqual(2, contentStore.Test.LiveGen); Assert.IsTrue(contentStore.Test.NextGen); @@ -1084,7 +1187,17 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT published ? rootKit.PublishedData : null); NuCacheContentService.ContentKits[1] = kit; - SnapshotService.Notify(new[] { new ContentCacheRefresher.JsonPayload(1, Guid.Empty, changeType) }, out _, out _); + SnapshotService.Notify( + new[] + { + new ContentCacheRefresher.JsonPayload() + { + Id = 1, + ChangeTypes = changeType + } + }, + out _, + out _); Assert.AreEqual(assertGen, contentStore.Test.LiveGen); Assert.IsTrue(contentStore.Test.NextGen); @@ -1162,9 +1275,13 @@ public class PublishedSnapshotServiceCollectionTests : PublishedSnapshotServiceT SnapshotService.Notify( new[] - { - new ContentCacheRefresher.JsonPayload(3, Guid.Empty, TreeChangeTypes.RefreshBranch), // remove middle child - }, + { + new ContentCacheRefresher.JsonPayload() // remove middle child + { + Id = 3, + ChangeTypes = TreeChangeTypes.RefreshBranch + } + }, out _, out _);