diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs index f5fc4e0867..cc224186aa 100644 --- a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs @@ -20,6 +20,7 @@ public sealed class ContentCacheRefresher : PayloadCacheRefresherBase descendantsKeys)) + { + var branchKeys = descendantsKeys.ToList(); + branchKeys.Add(key); + + foreach (Guid branchKey in branchKeys) + { + _documentCacheService.RefreshMemoryCacheAsync(branchKey).GetAwaiter().GetResult(); + } + } + } + + if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) + { + _documentCacheService.ClearMemoryCacheAsync(CancellationToken.None).GetAwaiter().GetResult(); + } + + if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) + { + _documentCacheService.RemoveFromMemoryCacheAsync(key).GetAwaiter().GetResult(); + } + } + private void HandleNavigation(JsonPayload payload) { diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentTypeCacheRefresher.cs index f9936933cc..35320f47d3 100644 --- a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentTypeCacheRefresher.cs @@ -3,6 +3,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Changes; @@ -15,6 +16,9 @@ public sealed class ContentTypeCacheRefresher : PayloadCacheRefresherBase { }); - + _publishedContentTypeCache.ClearContentTypes(payloads.Select(x => x.Id)); _publishedContentTypeFactory.NotifyDataTypeChanges(); + _publishedModelFactory.WithSafeLiveFactoryReset(() => + { + IEnumerable documentTypeIds = payloads.Where(x => x.ItemType == nameof(IContentType)).Select(x => x.Id); + IEnumerable mediaTypeIds = payloads.Where(x => x.ItemType == nameof(IMediaType)).Select(x => x.Id); + + _documentCacheService.RebuildMemoryCacheByContentTypeAsync(documentTypeIds); + _mediaCacheService.RebuildMemoryCacheByContentTypeAsync(mediaTypeIds); + }); // now we can trigger the event base.Refresh(payloads); diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/DataTypeCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/DataTypeCacheRefresher.cs index de25660fa0..bf45161caa 100644 --- a/src/Umbraco.Core/Cache/Refreshers/Implement/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/Refreshers/Implement/DataTypeCacheRefresher.cs @@ -3,6 +3,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; +using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Extensions; @@ -14,6 +15,9 @@ public sealed class DataTypeCacheRefresher : PayloadCacheRefresherBase dataTypeCache = AppCaches.IsolatedCaches.Get(); + List removedContentTypes = new(); foreach (JsonPayload payload in payloads) { _idKeyMap.ClearCache(payload.Id); @@ -84,14 +95,25 @@ public sealed class DataTypeCacheRefresher : PayloadCacheRefresherBase(payload.Id)); } - } - // TODO: We need to clear the HybridCache of any content using the ContentType, but NOT the database cache here, and this should be done within the "WithSafeLiveFactoryReset" to ensure that the factory is locked in the meantime. - _publishedModelFactory.WithSafeLiveFactoryReset(() => { }); + removedContentTypes.AddRange(_publishedContentTypeCache.ClearByDataTypeId(payload.Id)); + } var changedIds = payloads.Select(x => x.Id).ToArray(); _publishedContentTypeFactory.NotifyDataTypeChanges(changedIds); + _publishedModelFactory.WithSafeLiveFactoryReset(() => + { + IEnumerable documentTypeIds = removedContentTypes + .Where(x => x.ItemType == PublishedItemType.Content) + .Select(x => x.Id); + _documentCacheService.RebuildMemoryCacheByContentTypeAsync(documentTypeIds).GetAwaiter().GetResult(); + + IEnumerable mediaTypeIds = removedContentTypes + .Where(x => x.ItemType == PublishedItemType.Media) + .Select(x => x.Id); + _mediaCacheService.RebuildMemoryCacheByContentTypeAsync(mediaTypeIds); + }); base.Refresh(payloads); } diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/MediaCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/MediaCacheRefresher.cs index 581e77c52a..4b4384a84d 100644 --- a/src/Umbraco.Core/Cache/Refreshers/Implement/MediaCacheRefresher.cs +++ b/src/Umbraco.Core/Cache/Refreshers/Implement/MediaCacheRefresher.cs @@ -2,6 +2,7 @@ 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.PublishedCache; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.Changes; @@ -16,6 +17,7 @@ public sealed class MediaCacheRefresher : PayloadCacheRefresherBase descendantsKeys)) + { + var branchKeys = descendantsKeys.ToList(); + branchKeys.Add(key); + + foreach (Guid branchKey in branchKeys) + { + _mediaCacheService.RefreshMemoryCacheAsync(branchKey).GetAwaiter().GetResult(); + } + } + } + + if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll)) + { + _mediaCacheService.ClearMemoryCacheAsync(CancellationToken.None).GetAwaiter().GetResult(); + } + + if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) + { + _mediaCacheService.RemoveFromMemoryCacheAsync(key).GetAwaiter().GetResult(); + } + } + private void HandleNavigation(JsonPayload payload) { if (payload.Key is null) diff --git a/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs b/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs index 309c4560cf..9b1fde9826 100644 --- a/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/NuCacheSettings.cs @@ -19,6 +19,7 @@ public class NuCacheSettings /// /// Gets or sets a value defining the BTree block size. /// + [Obsolete("This property is no longer used")] public int? BTreeBlockSize { get; set; } /// @@ -37,8 +38,10 @@ public class NuCacheSettings /// The size to use for nucache Kit batches. Higher value means more content loaded into memory at a time. /// [DefaultValue(StaticKitBatchSize)] + [Obsolete("This property is no longer used")] public int KitBatchSize { get; set; } = StaticKitBatchSize; + [Obsolete("This property is no longer used")] public bool UnPublishedContentCompression { get; set; } = false; [DefaultValue(StaticUsePagedSqlQuery)] diff --git a/src/Umbraco.Core/Constants-Configuration.cs b/src/Umbraco.Core/Constants-Configuration.cs index 60b3397eeb..a569110f01 100644 --- a/src/Umbraco.Core/Constants-Configuration.cs +++ b/src/Umbraco.Core/Constants-Configuration.cs @@ -66,6 +66,7 @@ public static partial class Constants public const string ConfigPackageManifests = ConfigPrefix + "PackageManifests"; public const string ConfigWebhook = ConfigPrefix + "Webhook"; public const string ConfigCache = ConfigPrefix + "Cache"; + public const string ConfigCacheEntry = ConfigCache + ":Entry"; public static class NamedOptions { @@ -79,6 +80,13 @@ public static partial class Constants public const string MemberTypes = "MemberTypes"; } + + public static class CacheEntry + { + public const string Document = "Document"; + + public const string Media = "Media"; + } } } } diff --git a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs index 6c771f5023..a3b3ed7631 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -104,6 +104,10 @@ public static partial class UmbracoBuilderExtensions builder.Services.Configure( Constants.Configuration.NamedOptions.InstallDefaultData.MemberTypes, builder.Config.GetSection($"{Constants.Configuration.ConfigInstallDefaultData}:{Constants.Configuration.NamedOptions.InstallDefaultData.MemberTypes}")); + builder.Services.Configure(Constants.Configuration.NamedOptions.CacheEntry.Media, + builder.Config.GetSection($"{Constants.Configuration.ConfigCacheEntry}:{Constants.Configuration.NamedOptions.CacheEntry.Media}")); + builder.Services.Configure(Constants.Configuration.NamedOptions.CacheEntry.Document, + builder.Config.GetSection($"{Constants.Configuration.ConfigCacheEntry}:{Constants.Configuration.NamedOptions.CacheEntry.Document}")); // TODO: Remove this in V12 // This is to make the move of the AllowEditInvariantFromNonDefault setting from SecuritySettings to ContentSettings backwards compatible diff --git a/src/Umbraco.Core/Models/CacheEntrySettings.cs b/src/Umbraco.Core/Models/CacheEntrySettings.cs new file mode 100644 index 0000000000..21748b73f5 --- /dev/null +++ b/src/Umbraco.Core/Models/CacheEntrySettings.cs @@ -0,0 +1,19 @@ +using System.ComponentModel; + +namespace Umbraco.Cms.Core.Models; + +public class CacheEntrySettings +{ + internal const string StaticLocalCacheDuration = "1.00:00:00"; + internal const string StaticRemoteCacheDuration = "365.00:00:00"; + internal const string StaticSeedCacheDuration = "365.00:00:00"; + + [DefaultValue(StaticLocalCacheDuration)] + public TimeSpan LocalCacheDuration { get; set; } = TimeSpan.Parse(StaticLocalCacheDuration); + + [DefaultValue(StaticRemoteCacheDuration)] + public TimeSpan RemoteCacheDuration { get; set; } = TimeSpan.Parse(StaticRemoteCacheDuration); + + [DefaultValue(StaticSeedCacheDuration)] + public TimeSpan SeedCacheDuration { get; set; } = TimeSpan.Parse(StaticSeedCacheDuration); +} diff --git a/src/Umbraco.Core/Models/CacheSettings.cs b/src/Umbraco.Core/Models/CacheSettings.cs index 2d4373a4da..f478756f0c 100644 --- a/src/Umbraco.Core/Models/CacheSettings.cs +++ b/src/Umbraco.Core/Models/CacheSettings.cs @@ -7,7 +7,6 @@ namespace Umbraco.Cms.Core.Models; public class CacheSettings { internal const int StaticDocumentBreadthFirstSeedCount = 100; - internal const int StaticMediaBreadthFirstSeedCount = 100; internal const string StaticSeedCacheDuration = "365.00:00:00"; @@ -20,10 +19,10 @@ public class CacheSettings [DefaultValue(StaticDocumentBreadthFirstSeedCount)] public int DocumentBreadthFirstSeedCount { get; set; } = StaticDocumentBreadthFirstSeedCount; - [DefaultValue(StaticMediaBreadthFirstSeedCount)] public int MediaBreadthFirstSeedCount { get; set; } = StaticDocumentBreadthFirstSeedCount; + [Obsolete("Use Cache:Entry:Document:SeedCacheDuration instead")] [DefaultValue(StaticSeedCacheDuration)] public TimeSpan SeedCacheDuration { get; set; } = TimeSpan.Parse(StaticSeedCacheDuration); } diff --git a/src/Umbraco.PublishedCache.HybridCache/Services/IDocumentCacheService.cs b/src/Umbraco.Core/PublishedCache/IDocumentCacheService.cs similarity index 59% rename from src/Umbraco.PublishedCache.HybridCache/Services/IDocumentCacheService.cs rename to src/Umbraco.Core/PublishedCache/IDocumentCacheService.cs index 18a25496e2..d17c628063 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Services/IDocumentCacheService.cs +++ b/src/Umbraco.Core/PublishedCache/IDocumentCacheService.cs @@ -1,7 +1,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Infrastructure.HybridCache.Services; +namespace Umbraco.Cms.Core.PublishedCache; public interface IDocumentCacheService { @@ -19,5 +19,13 @@ public interface IDocumentCacheService void Rebuild(IReadOnlyCollection contentTypeIds); - internal IEnumerable GetByContentType(IPublishedContentType contentType); + IEnumerable GetByContentType(IPublishedContentType contentType); + + Task ClearMemoryCacheAsync(CancellationToken cancellationToken); + + Task RefreshMemoryCacheAsync(Guid key); + + Task RemoveFromMemoryCacheAsync(Guid key); + + Task RebuildMemoryCacheByContentTypeAsync(IEnumerable contentTypeIds); } diff --git a/src/Umbraco.PublishedCache.HybridCache/Services/IMediaCacheService.cs b/src/Umbraco.Core/PublishedCache/IMediaCacheService.cs similarity index 62% rename from src/Umbraco.PublishedCache.HybridCache/Services/IMediaCacheService.cs rename to src/Umbraco.Core/PublishedCache/IMediaCacheService.cs index abaf0f28b5..13d654676c 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Services/IMediaCacheService.cs +++ b/src/Umbraco.Core/PublishedCache/IMediaCacheService.cs @@ -1,7 +1,7 @@ using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Umbraco.Cms.Infrastructure.HybridCache.Services; +namespace Umbraco.Cms.Core.PublishedCache; public interface IMediaCacheService { @@ -13,6 +13,14 @@ public interface IMediaCacheService Task RefreshMediaAsync(IMedia media); + Task RebuildMemoryCacheByContentTypeAsync(IEnumerable mediaTypeIds); + + Task ClearMemoryCacheAsync(CancellationToken cancellationToken); + + Task RefreshMemoryCacheAsync(Guid key); + + Task RemoveFromMemoryCacheAsync(Guid key); + Task DeleteItemAsync(IContentBase media); Task SeedAsync(CancellationToken cancellationToken); diff --git a/src/Umbraco.Core/PublishedCache/IPublishedContentTypeCache.cs b/src/Umbraco.Core/PublishedCache/IPublishedContentTypeCache.cs index 318e7046c1..39c02d9f2f 100644 --- a/src/Umbraco.Core/PublishedCache/IPublishedContentTypeCache.cs +++ b/src/Umbraco.Core/PublishedCache/IPublishedContentTypeCache.cs @@ -15,12 +15,35 @@ public interface IPublishedContentTypeCache /// An identifier. public void ClearContentType(int id); + /// + /// Clears cached content types. + /// + /// ContentType IDs to clear + public void ClearContentTypes(IEnumerable ids) + { + foreach (var id in ids) + { + ClearContentType(id); + } + } + /// /// Clears all cached content types referencing a data type. /// /// A data type identifier. public void ClearDataType(int id); + /// + /// Clears all cached content types referencing a data type. + /// + /// The data type id to remove content types by + /// The removed content types + public IEnumerable ClearByDataTypeId(int id) + { + ClearDataType(id); + return []; + } + /// /// Gets a published content type. /// diff --git a/src/Umbraco.Core/Services/DocumentUrlService.cs b/src/Umbraco.Core/Services/DocumentUrlService.cs index 0d6fb168fa..dc5e1174ca 100644 --- a/src/Umbraco.Core/Services/DocumentUrlService.cs +++ b/src/Umbraco.Core/Services/DocumentUrlService.cs @@ -440,7 +440,7 @@ public class DocumentUrlService : IDocumentUrlService return "#"; } - var cultureOrDefault = culture ?? _languageService.GetDefaultIsoCodeAsync().GetAwaiter().GetResult(); + var cultureOrDefault = string.IsNullOrWhiteSpace(culture) is false ? culture : _languageService.GetDefaultIsoCodeAsync().GetAwaiter().GetResult(); Guid[] ancestorsOrSelfKeysArray = ancestorsOrSelfKeys as Guid[] ?? ancestorsOrSelfKeys.ToArray(); IDictionary ancestorOrSelfKeyToDomains = ancestorsOrSelfKeysArray.ToDictionary(x => x, ancestorKey => diff --git a/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs b/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs index 204f7a910c..c101e2a0e9 100644 --- a/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs +++ b/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs @@ -120,11 +120,21 @@ public class PublishedContentTypeCache : IPublishedContentTypeCache } } + public void ClearContentTypes(IEnumerable ids) + { + foreach (var id in ids) + { + ClearContentType(id); + } + } + /// /// Clears all cached content types referencing a data type. /// /// A data type identifier. - public void ClearDataType(int id) + public void ClearDataType(int id) => ClearByDataTypeId(id); + + public IEnumerable ClearByDataTypeId(int id) { if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) { @@ -135,11 +145,12 @@ public class PublishedContentTypeCache : IPublishedContentTypeCache // properties ie both its own properties and those that were inherited (it's based upon an // IContentTypeComposition) and so every PublishedContentType having a property based upon // the cleared data type, be it local or inherited, will be cleared. + IPublishedContentType[] toRemove; try { _lock.EnterWriteLock(); - IPublishedContentType[] toRemove = _typesById.Values + toRemove = _typesById.Values .Where(x => x.PropertyTypes.Any(xx => xx.DataType.Id == id)).ToArray(); foreach (IPublishedContentType type in toRemove) { @@ -154,6 +165,8 @@ public class PublishedContentTypeCache : IPublishedContentTypeCache _lock.ExitWriteLock(); } } + + return toRemove; } /// diff --git a/src/Umbraco.Infrastructure/Routing/RedirectTracker.cs b/src/Umbraco.Infrastructure/Routing/RedirectTracker.cs index 067fa91fa4..ee752af798 100644 --- a/src/Umbraco.Infrastructure/Routing/RedirectTracker.cs +++ b/src/Umbraco.Infrastructure/Routing/RedirectTracker.cs @@ -14,30 +14,30 @@ namespace Umbraco.Cms.Infrastructure.Routing { internal class RedirectTracker : IRedirectTracker { - private readonly IUmbracoContextFactory _umbracoContextFactory; private readonly IVariationContextAccessor _variationContextAccessor; private readonly ILocalizationService _localizationService; private readonly IRedirectUrlService _redirectUrlService; private readonly IPublishedContentCache _contentCache; private readonly IDocumentNavigationQueryService _navigationQueryService; private readonly ILogger _logger; + private readonly IDocumentUrlService _documentUrlService; public RedirectTracker( - IUmbracoContextFactory umbracoContextFactory, IVariationContextAccessor variationContextAccessor, ILocalizationService localizationService, IRedirectUrlService redirectUrlService, IPublishedContentCache contentCache, IDocumentNavigationQueryService navigationQueryService, - ILogger logger) + ILogger logger, + IDocumentUrlService documentUrlService) { - _umbracoContextFactory = umbracoContextFactory; _variationContextAccessor = variationContextAccessor; _localizationService = localizationService; _redirectUrlService = redirectUrlService; _contentCache = contentCache; _navigationQueryService = navigationQueryService; _logger = logger; + _documentUrlService = documentUrlService; } /// @@ -98,19 +98,11 @@ namespace Umbraco.Cms.Infrastructure.Routing return; } - using UmbracoContextReference reference = _umbracoContextFactory.EnsureUmbracoContext(); - IPublishedContentCache? contentCache = reference.UmbracoContext.Content; - if (contentCache == null) - { - _logger.LogWarning("Could not track redirects because there is no published content cache available on the current published snapshot."); - return; - } - foreach (((int contentId, string culture), (Guid contentKey, string oldRoute)) in oldRoutes) { try { - var newRoute = contentCache.GetRouteById(contentId, culture); + var newRoute = _documentUrlService.GetLegacyRouteFormat(contentKey, culture, false); if (!IsValidRoute(newRoute) || oldRoute == newRoute) { continue; diff --git a/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/SeedingNotificationHandler.cs b/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/SeedingNotificationHandler.cs index 196dd3950e..47f590125f 100644 --- a/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/SeedingNotificationHandler.cs +++ b/src/Umbraco.PublishedCache.HybridCache/NotificationHandlers/SeedingNotificationHandler.cs @@ -3,6 +3,7 @@ using Umbraco.Cms.Core; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.HybridCache.Services; diff --git a/src/Umbraco.PublishedCache.HybridCache/Persistence/DatabaseCacheRepository.cs b/src/Umbraco.PublishedCache.HybridCache/Persistence/DatabaseCacheRepository.cs index 30d1358d64..63ccf00a26 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Persistence/DatabaseCacheRepository.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Persistence/DatabaseCacheRepository.cs @@ -161,6 +161,16 @@ AND cmsContentNu.nodeId IS NULL return count == 0; } + public async Task> GetContentKeysAsync(Guid nodeObjectType) + { + Sql sql = Sql() + .Select(x => x.UniqueId) + .From() + .Where(x => x.NodeObjectType == nodeObjectType); + + return await Database.FetchAsync(sql); + } + // assumes member tree lock public bool VerifyMemberDbCache() { @@ -235,8 +245,13 @@ AND cmsContentNu.nodeId IS NULL return []; } - Sql? sql = SqlContentSourcesSelect() - .InnerJoin("n") + Sql sql = objectType == Constants.ObjectTypes.Document + ? SqlContentSourcesSelect() + : objectType == Constants.ObjectTypes.Media + ? SqlMediaSourcesSelect() + : throw new ArgumentOutOfRangeException(nameof(objectType), objectType, null); + + sql.InnerJoin("n") .On((n, c) => n.NodeId == c.ContentTypeId, "n", "umbracoContent") .Append(SqlObjectTypeNotTrashed(SqlContext, objectType)) .WhereIn(x => x.UniqueId, keys,"n") @@ -251,7 +266,6 @@ AND cmsContentNu.nodeId IS NULL { ContentCacheDataSerializerEntityType.Document => Constants.ObjectTypes.Document, ContentCacheDataSerializerEntityType.Media => Constants.ObjectTypes.Media, - ContentCacheDataSerializerEntityType.Member => Constants.ObjectTypes.Member, _ => throw new ArgumentOutOfRangeException(nameof(entityType), entityType, null), }; @@ -262,7 +276,15 @@ AND cmsContentNu.nodeId IS NULL foreach (ContentSourceDto row in dtos) { - yield return CreateContentNodeKit(row, serializer, row.Published is false); + if (entityType == ContentCacheDataSerializerEntityType.Document) + { + yield return CreateContentNodeKit(row, serializer, row.Published is false); + } + else + { + yield return CreateMediaNodeKit(row, serializer); + } + } } diff --git a/src/Umbraco.PublishedCache.HybridCache/Persistence/IDatabaseCacheRepository.cs b/src/Umbraco.PublishedCache.HybridCache/Persistence/IDatabaseCacheRepository.cs index 0b5b739ac3..93a589d926 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Persistence/IDatabaseCacheRepository.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Persistence/IDatabaseCacheRepository.cs @@ -67,4 +67,6 @@ internal interface IDatabaseCacheRepository /// Rebuilds the caches for content, media and/or members based on the content type ids specified /// bool VerifyMediaDbCache(); + + Task> GetContentKeysAsync(Guid nodeObjectType); } diff --git a/src/Umbraco.PublishedCache.HybridCache/Services/DocumentCacheService.cs b/src/Umbraco.PublishedCache.HybridCache/Services/DocumentCacheService.cs index 011f0cd24b..135d2f99dd 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Services/DocumentCacheService.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Services/DocumentCacheService.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.HybridCache.Factories; @@ -23,8 +24,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService private readonly IEnumerable _seedKeyProviders; private readonly IPublishedModelFactory _publishedModelFactory; private readonly IPreviewService _previewService; - private readonly CacheSettings _cacheSettings; - + private readonly CacheEntrySettings _cacheEntrySettings; private HashSet? _seedKeys; private HashSet SeedKeys { @@ -54,7 +54,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService IPublishedContentFactory publishedContentFactory, ICacheNodeFactory cacheNodeFactory, IEnumerable seedKeyProviders, - IOptions cacheSettings, + IOptionsMonitor cacheEntrySettings, IPublishedModelFactory publishedModelFactory, IPreviewService previewService) { @@ -67,7 +67,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService _seedKeyProviders = seedKeyProviders; _publishedModelFactory = publishedModelFactory; _previewService = previewService; - _cacheSettings = cacheSettings.Value; + _cacheEntrySettings = cacheEntrySettings.Get(Constants.Configuration.NamedOptions.CacheEntry.Document); } public async Task GetByKeyAsync(Guid key, bool? preview = null) @@ -82,7 +82,8 @@ internal sealed class DocumentCacheService : IDocumentCacheService ContentCacheNode? contentCacheNode = await _databaseCacheRepository.GetContentSourceAsync(key, calculatedPreview); scope.Complete(); return contentCacheNode; - }); + }, + GetEntryOptions(key)); return contentCacheNode is null ? null : _publishedContentFactory.ToIPublishedContent(contentCacheNode, calculatedPreview).CreateModel(_publishedModelFactory); } @@ -101,6 +102,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService } bool calculatedPreview = preview ?? GetPreview(); + Guid key = keyAttempt.Result; ContentCacheNode? contentCacheNode = await _hybridCache.GetOrCreateAsync( GetCacheKey(keyAttempt.Result, calculatedPreview), // Unique key to the cache entry @@ -110,7 +112,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService ContentCacheNode? contentCacheNode = await _databaseCacheRepository.GetContentSourceAsync(id, calculatedPreview); scope.Complete(); return contentCacheNode; - }); + }, GetEntryOptions(key)); return contentCacheNode is null ? null : _publishedContentFactory.ToIPublishedContent(contentCacheNode, calculatedPreview).CreateModel(_publishedModelFactory);; } @@ -126,6 +128,57 @@ internal sealed class DocumentCacheService : IDocumentCacheService .WhereNotNull(); } + public async Task ClearMemoryCacheAsync(CancellationToken cancellationToken) + { + // TODO: This should be done with tags, however this is not implemented yet, so for now we have to naively get all content keys and clear them all. + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + + // We have to get ALL document keys in order to be able to remove them from the cache, + IEnumerable documentKeys = await _databaseCacheRepository.GetContentKeysAsync(Constants.ObjectTypes.Document); + + foreach (Guid documentKey in documentKeys) + { + if (cancellationToken.IsCancellationRequested) + { + return; + } + + // We'll remove both the draft and published cache + await _hybridCache.RemoveAsync(GetCacheKey(documentKey, false), cancellationToken); + await _hybridCache.RemoveAsync(GetCacheKey(documentKey, true), cancellationToken); + } + + // We have to run seeding again after the cache is cleared + await SeedAsync(cancellationToken); + + scope.Complete(); + } + + public async Task RefreshMemoryCacheAsync(Guid key) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + + ContentCacheNode? draftNode = await _databaseCacheRepository.GetContentSourceAsync(key, true); + if (draftNode is not null) + { + await _hybridCache.SetAsync(GetCacheKey(draftNode.Key, true), draftNode, GetEntryOptions(draftNode.Key)); + } + + ContentCacheNode? publishedNode = await _databaseCacheRepository.GetContentSourceAsync(key, false); + if (publishedNode is not null) + { + await _hybridCache.SetAsync(GetCacheKey(publishedNode.Key, false), publishedNode, GetEntryOptions(publishedNode.Key)); + } + + scope.Complete(); + } + + public async Task RemoveFromMemoryCacheAsync(Guid key) + { + await _hybridCache.RemoveAsync(GetCacheKey(key, true)); + await _hybridCache.RemoveAsync(GetCacheKey(key, false)); + } + public async Task SeedAsync(CancellationToken cancellationToken) { foreach (Guid key in SeedKeys) @@ -155,7 +208,8 @@ internal sealed class DocumentCacheService : IDocumentCacheService return cacheNode; }, - GetSeedEntryOptions()); + GetSeedEntryOptions(), + cancellationToken: cancellationToken); // If the value is null, it's likely because if (cachedValue is null) @@ -167,10 +221,24 @@ internal sealed class DocumentCacheService : IDocumentCacheService private HybridCacheEntryOptions GetSeedEntryOptions() => new() { - Expiration = _cacheSettings.SeedCacheDuration, - LocalCacheExpiration = _cacheSettings.SeedCacheDuration + Expiration = _cacheEntrySettings.SeedCacheDuration, + LocalCacheExpiration = _cacheEntrySettings.SeedCacheDuration }; + private HybridCacheEntryOptions GetEntryOptions(Guid key) + { + if (SeedKeys.Contains(key)) + { + return GetSeedEntryOptions(); + } + + return new HybridCacheEntryOptions + { + Expiration = _cacheEntrySettings.RemoteCacheDuration, + LocalCacheExpiration = _cacheEntrySettings.LocalCacheDuration, + }; + } + public async Task HasContentByIdAsync(int id, bool preview = false) { Attempt keyAttempt = _idKeyMap.GetKeyForId(id, UmbracoObjectTypes.Document); @@ -195,67 +263,29 @@ internal sealed class DocumentCacheService : IDocumentCacheService { using ICoreScope scope = _scopeProvider.CreateCoreScope(); - bool isSeeded = SeedKeys.Contains(content.Key); - // Always set draft node // We have nodes seperate in the cache, cause 99% of the time, you are only using one // and thus we won't get too much data when retrieving from the cache. ContentCacheNode draftCacheNode = _cacheNodeFactory.ToContentCacheNode(content, true); await _databaseCacheRepository.RefreshContentAsync(draftCacheNode, content.PublishedState); - _scopeProvider.Context?.Enlist($"UpdateMemoryCache_Draft_{content.Key}", completed => - { - if(completed is false) - { - return; - } - - RefreshHybridCache(draftCacheNode, GetCacheKey(content.Key, true), isSeeded).GetAwaiter().GetResult(); - }, 1); if (content.PublishedState == PublishedState.Publishing) { var publishedCacheNode = _cacheNodeFactory.ToContentCacheNode(content, false); await _databaseCacheRepository.RefreshContentAsync(publishedCacheNode, content.PublishedState); - _scopeProvider.Context?.Enlist($"UpdateMemoryCache_{content.Key}", completed => - { - if(completed is false) - { - return; - } - - RefreshHybridCache(publishedCacheNode, GetCacheKey(content.Key, false), isSeeded).GetAwaiter().GetResult(); - }, 1); } scope.Complete(); } - private async Task RefreshHybridCache(ContentCacheNode cacheNode, string cacheKey, bool isSeeded) - { - // If it's seeded we want it to stick around the cache for longer. - if (isSeeded) - { - await _hybridCache.SetAsync( - cacheKey, - cacheNode, - GetSeedEntryOptions()); - } - else - { - await _hybridCache.SetAsync(cacheKey, cacheNode); - } - } - private string GetCacheKey(Guid key, bool preview) => preview ? $"{key}+draft" : $"{key}"; public async Task DeleteItemAsync(IContentBase content) { using ICoreScope scope = _scopeProvider.CreateCoreScope(); await _databaseCacheRepository.DeleteContentItemAsync(content.Id); - await _hybridCache.RemoveAsync(GetCacheKey(content.Key, true)); - await _hybridCache.RemoveAsync(GetCacheKey(content.Key, false)); scope.Complete(); } @@ -263,6 +293,14 @@ internal sealed class DocumentCacheService : IDocumentCacheService { using ICoreScope scope = _scopeProvider.CreateCoreScope(); _databaseCacheRepository.Rebuild(contentTypeIds.ToList()); + RebuildMemoryCacheByContentTypeAsync(contentTypeIds).GetAwaiter().GetResult(); + scope.Complete(); + } + + public async Task RebuildMemoryCacheByContentTypeAsync(IEnumerable contentTypeIds) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + IEnumerable contentByContentTypeKey = _databaseCacheRepository.GetContentByContentTypeKey(contentTypeIds.Select(x => _idKeyMap.GetKeyForId(x, UmbracoObjectTypes.DocumentType).Result), ContentCacheDataSerializerEntityType.Document); scope.Complete(); @@ -276,6 +314,5 @@ internal sealed class DocumentCacheService : IDocumentCacheService } } - } } diff --git a/src/Umbraco.PublishedCache.HybridCache/Services/MediaCacheService.cs b/src/Umbraco.PublishedCache.HybridCache/Services/MediaCacheService.cs index 024413f2c5..12327489b8 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Services/MediaCacheService.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Services/MediaCacheService.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Options; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Scoping; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Infrastructure.HybridCache.Factories; @@ -22,7 +23,7 @@ internal class MediaCacheService : IMediaCacheService private readonly ICacheNodeFactory _cacheNodeFactory; private readonly IEnumerable _seedKeyProviders; private readonly IPublishedModelFactory _publishedModelFactory; - private readonly CacheSettings _cacheSettings; + private readonly CacheEntrySettings _cacheEntrySettings; private HashSet? _seedKeys; private HashSet SeedKeys @@ -54,7 +55,7 @@ internal class MediaCacheService : IMediaCacheService ICacheNodeFactory cacheNodeFactory, IEnumerable seedKeyProviders, IPublishedModelFactory publishedModelFactory, - IOptions cacheSettings) + IOptionsMonitor cacheEntrySettings) { _databaseCacheRepository = databaseCacheRepository; _idKeyMap = idKeyMap; @@ -64,7 +65,7 @@ internal class MediaCacheService : IMediaCacheService _cacheNodeFactory = cacheNodeFactory; _seedKeyProviders = seedKeyProviders; _publishedModelFactory = publishedModelFactory; - _cacheSettings = cacheSettings.Value; + _cacheEntrySettings = cacheEntrySettings.Get(Constants.Configuration.NamedOptions.CacheEntry.Media); } public async Task GetByKeyAsync(Guid key) @@ -83,7 +84,7 @@ internal class MediaCacheService : IMediaCacheService ContentCacheNode? mediaCacheNode = await _databaseCacheRepository.GetMediaSourceAsync(idAttempt.Result); scope.Complete(); return mediaCacheNode; - }); + }, GetEntryOptions(key)); return contentCacheNode is null ? null : _publishedContentFactory.ToIPublishedMedia(contentCacheNode).CreateModel(_publishedModelFactory); } @@ -95,6 +96,7 @@ internal class MediaCacheService : IMediaCacheService { return null; } + Guid key = keyAttempt.Result; ContentCacheNode? contentCacheNode = await _hybridCache.GetOrCreateAsync( $"{keyAttempt.Result}", // Unique key to the cache entry @@ -104,7 +106,7 @@ internal class MediaCacheService : IMediaCacheService ContentCacheNode? mediaCacheNode = await _databaseCacheRepository.GetMediaSourceAsync(id); scope.Complete(); return mediaCacheNode; - }); + }, GetEntryOptions(key)); return contentCacheNode is null ? null : _publishedContentFactory.ToIPublishedMedia(contentCacheNode).CreateModel(_publishedModelFactory); } @@ -137,7 +139,6 @@ internal class MediaCacheService : IMediaCacheService // We have nodes seperate in the cache, cause 99% of the time, you are only using one // and thus we won't get too much data when retrieving from the cache. var cacheNode = _cacheNodeFactory.ToContentCacheNode(media); - await _hybridCache.SetAsync(GetCacheKey(media.Key, false), cacheNode); await _databaseCacheRepository.RefreshMediaAsync(cacheNode); scope.Complete(); } @@ -146,7 +147,6 @@ internal class MediaCacheService : IMediaCacheService { using ICoreScope scope = _scopeProvider.CreateCoreScope(); await _databaseCacheRepository.DeleteContentItemAsync(media.Id); - await _hybridCache.RemoveAsync(media.Key.ToString()); scope.Complete(); } @@ -180,6 +180,65 @@ internal class MediaCacheService : IMediaCacheService } } + public async Task RefreshMemoryCacheAsync(Guid key) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + + ContentCacheNode? publishedNode = await _databaseCacheRepository.GetMediaSourceAsync(key); + if (publishedNode is not null) + { + await _hybridCache.SetAsync(GetCacheKey(publishedNode.Key, false), publishedNode, GetEntryOptions(publishedNode.Key)); + } + + scope.Complete(); + } + + public async Task ClearMemoryCacheAsync(CancellationToken cancellationToken) + { + // TODO: This should be done with tags, however this is not implemented yet, so for now we have to naively get all content keys and clear them all. + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + + // We have to get ALL document keys in order to be able to remove them from the cache, + IEnumerable documentKeys = await _databaseCacheRepository.GetContentKeysAsync(Constants.ObjectTypes.Media); + + foreach (Guid documentKey in documentKeys) + { + if (cancellationToken.IsCancellationRequested) + { + return; + } + + // We'll remove both the draft and published cache + await _hybridCache.RemoveAsync(GetCacheKey(documentKey, false), cancellationToken); + } + + // We have to run seeding again after the cache is cleared + await SeedAsync(cancellationToken); + + scope.Complete(); + } + + public async Task RemoveFromMemoryCacheAsync(Guid key) + => await _hybridCache.RemoveAsync(GetCacheKey(key, false)); + + public async Task RebuildMemoryCacheByContentTypeAsync(IEnumerable mediaTypeIds) + { + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + + IEnumerable contentByContentTypeKey = _databaseCacheRepository.GetContentByContentTypeKey(mediaTypeIds.Select(x => _idKeyMap.GetKeyForId(x, UmbracoObjectTypes.MediaType).Result), ContentCacheDataSerializerEntityType.Media); + + foreach (ContentCacheNode content in contentByContentTypeKey) + { + _hybridCache.RemoveAsync(GetCacheKey(content.Key, true)).GetAwaiter().GetResult(); + + if (content.IsDraft is false) + { + _hybridCache.RemoveAsync(GetCacheKey(content.Key, false)).GetAwaiter().GetResult(); + } + } + scope.Complete(); + } + public void Rebuild(IReadOnlyCollection contentTypeIds) { using ICoreScope scope = _scopeProvider.CreateCoreScope(); @@ -200,10 +259,25 @@ internal class MediaCacheService : IMediaCacheService scope.Complete(); } + private HybridCacheEntryOptions GetEntryOptions(Guid key) + { + if (SeedKeys.Contains(key)) + { + return GetSeedEntryOptions(); + } + + return new HybridCacheEntryOptions + { + Expiration = _cacheEntrySettings.RemoteCacheDuration, + LocalCacheExpiration = _cacheEntrySettings.LocalCacheDuration, + }; + } + + private HybridCacheEntryOptions GetSeedEntryOptions() => new() { - Expiration = _cacheSettings.SeedCacheDuration, - LocalCacheExpiration = _cacheSettings.SeedCacheDuration, + Expiration = _cacheEntrySettings.SeedCacheDuration, + LocalCacheExpiration = _cacheEntrySettings.SeedCacheDuration, }; private string GetCacheKey(Guid key, bool preview) => preview ? $"{key}+draft" : $"{key}"; diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockEditorElementVariationTestBase.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockEditorElementVariationTestBase.cs index daada604eb..74e7ea77a9 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockEditorElementVariationTestBase.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/PropertyEditors/BlockEditorElementVariationTestBase.cs @@ -7,18 +7,19 @@ using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; -using Umbraco.Cms.Core.Services.Changes; +using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Core.Web; -using Umbraco.Cms.Infrastructure.HybridCache.Services; using Umbraco.Cms.Tests.Common; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; +using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.PropertyEditors; @@ -63,6 +64,9 @@ public abstract class BlockEditorElementVariationTestBase : UmbracoIntegrationTe builder.Services.Configure(config => config.AllowEditInvariantFromNonDefault = TestsRequiringAllowEditInvariantFromNonDefault.Contains(TestContext.CurrentContext.Test.Name)); + + builder.AddNotificationHandler(); + builder.Services.AddUnique(); } [SetUp] diff --git a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheDocumentTypeTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheDocumentTypeTests.cs index c316b00ae6..17b55019db 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheDocumentTypeTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheDocumentTypeTests.cs @@ -1,9 +1,13 @@ using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; +using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache; @@ -11,7 +15,11 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache; [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class DocumentHybridCacheDocumentTypeTests : UmbracoIntegrationTestWithContentEditing { - protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.AddUmbracoHybridCache(); + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + builder.Services.AddUnique(); + } private IPublishedContentCache PublishedContentHybridCache => GetRequiredService(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheMockTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheMockTests.cs index 2e810114d0..5c38d4e4b0 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheMockTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheMockTests.cs @@ -94,6 +94,8 @@ public class DocumentHybridCacheMockTests : UmbracoIntegrationTestWithContent }); _mockedNucacheRepository.Setup(r => r.DeleteContentItemAsync(It.IsAny())); + var optionsMonitorMock = new Mock>(); + optionsMonitorMock.Setup(x => x.Get(It.IsAny())).Returns(new CacheEntrySettings()); _mockDocumentCacheService = new DocumentCacheService( _mockedNucacheRepository.Object, @@ -103,7 +105,7 @@ public class DocumentHybridCacheMockTests : UmbracoIntegrationTestWithContent GetRequiredService(), GetRequiredService(), GetSeedProviders(), - Options.Create(new CacheSettings()), + optionsMonitorMock.Object, GetRequiredService(), GetRequiredService()); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCachePropertyTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCachePropertyTest.cs index 1471234c8d..b5dff7085e 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCachePropertyTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCachePropertyTest.cs @@ -1,16 +1,20 @@ using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.ContentPublishing; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.ContentTypeEditing; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; +using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache; @@ -18,7 +22,11 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache; [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class DocumentHybridCachePropertyTest : UmbracoIntegrationTest { - protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.AddUmbracoHybridCache(); + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + builder.Services.AddUnique(); + } private ICacheManager CacheManager => GetRequiredService(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheTemplateTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheTemplateTests.cs index a0873bd983..c1291b1fff 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheTemplateTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheTemplateTests.cs @@ -1,18 +1,27 @@ using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.OperationStatus; +using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; +using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; + namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache; [TestFixture] [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class DocumentHybridCacheTemplateTests : UmbracoIntegrationTestWithContentEditing { - protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.AddUmbracoHybridCache(); + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + builder.Services.AddUnique(); + } private IPublishedContentCache PublishedContentHybridCache => GetRequiredService(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheTests.cs index 787fecaddf..34660435a7 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheTests.cs @@ -1,11 +1,15 @@ using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; +using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache; @@ -13,7 +17,11 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache; [UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] public class DocumentHybridCacheTests : UmbracoIntegrationTestWithContentEditing { - protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.AddUmbracoHybridCache(); + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + builder.Services.AddUnique(); + } private IPublishedContentCache PublishedContentHybridCache => GetRequiredService(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheVariantsTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheVariantsTests.cs index 87815fdbeb..a60270ab2f 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheVariantsTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/DocumentHybridCacheVariantsTests.cs @@ -1,15 +1,19 @@ using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Services.ContentTypeEditing; +using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Tests.Common.Builders; using Umbraco.Cms.Tests.Common.Builders.Extensions; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; +using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache; @@ -36,7 +40,11 @@ public class DocumentHybridCacheVariantsTests : UmbracoIntegrationTest private IContent VariantPage { get; set; } - protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.AddUmbracoHybridCache(); + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + builder.Services.AddUnique(); + } [SetUp] public async Task Setup() => await CreateTestData(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/MediaHybridCacheMediaTypeTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/MediaHybridCacheMediaTypeTests.cs index 728f940169..60ac4157dd 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/MediaHybridCacheMediaTypeTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/MediaHybridCacheMediaTypeTests.cs @@ -1,10 +1,14 @@ using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.PublishedCache; using Umbraco.Cms.Core.Services.ContentTypeEditing; +using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Tests.Common.TestHelpers; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; +using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache; @@ -16,7 +20,11 @@ public class MediaHybridCacheMediaTypeTests : UmbracoIntegrationTestWithMediaEdi private new IMediaTypeEditingService MediaTypeEditingService => GetRequiredService(); - protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.AddUmbracoHybridCache(); + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + builder.Services.AddUnique(); + } [Test] public async Task Cannot_Get_Property_From_Media_After_It_Is_Removed_From_MediaType_By_Id() diff --git a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/MediaHybridCacheTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/MediaHybridCacheTests.cs index f5edf65762..37263d913d 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/MediaHybridCacheTests.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/MediaHybridCacheTests.cs @@ -1,9 +1,13 @@ using NUnit.Framework; using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Sync; using Umbraco.Cms.Tests.Common.Testing; using Umbraco.Cms.Tests.Integration.Testing; +using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services; namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache; @@ -13,7 +17,11 @@ public class MediaHybridCacheTests : UmbracoIntegrationTestWithMediaEditing { private IPublishedMediaCache PublishedMediaHybridCache => GetRequiredService(); - protected override void CustomTestSetup(IUmbracoBuilder builder) => builder.AddUmbracoHybridCache(); + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + builder.Services.AddUnique(); + } // Media with crops [Test] diff --git a/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.HybridCache/DocumentBreadthFirstKeyProviderTests.cs b/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.HybridCache/DocumentBreadthFirstKeyProviderTests.cs index fef4486863..241ee77479 100644 --- a/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.HybridCache/DocumentBreadthFirstKeyProviderTests.cs +++ b/tests/Umbraco.Tests.UnitTests/Umbraco.PublishedCache.HybridCache/DocumentBreadthFirstKeyProviderTests.cs @@ -10,7 +10,6 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.PublishedCache.HybridCache; [TestFixture] public class DocumentBreadthFirstKeyProviderTests { - [Test] public void ZeroSeedCountReturnsZeroKeys() { @@ -22,7 +21,6 @@ public class DocumentBreadthFirstKeyProviderTests navigationQueryService.Setup(x => x.TryGetRootKeys(out rootKeyList)).Returns(true); navigationQueryService.Setup(x => x.TryGetChildrenKeys(It.IsAny(), out rootChildren)).Returns(true); - var cacheSettings = new CacheSettings { DocumentBreadthFirstSeedCount = 0 }; var sut = new DocumentBreadthFirstKeyProvider(navigationQueryService.Object, Options.Create(cacheSettings));