diff --git a/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs b/src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs index f5fc4e086785..cc224186aacf 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 f9936933cc8e..35320f47d387 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.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 de25660fa0d5..bf45161caa38 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.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 override void Refresh(JsonPayload[] payloads) { dataTypeCache.Result?.Clear(RepositoryCacheKeys.GetKey(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 581e77c52a1e..4b4384a84d0f 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.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 309c4560cfab..9b1fde982622 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 60b3397eeb29..a569110f01d3 100644 --- a/src/Umbraco.Core/Constants-Configuration.cs +++ b/src/Umbraco.Core/Constants-Configuration.cs @@ -66,6 +66,7 @@ public static class Configuration 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 class InstallDefaultData 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 6c771f502376..a3b3ed763133 100644 --- a/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs +++ b/src/Umbraco.Core/DependencyInjection/UmbracoBuilder.Configuration.cs @@ -104,6 +104,10 @@ public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder) 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 000000000000..21748b73f5b2 --- /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 2d4373a4da5f..f478756f0ca5 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 18a25496e234..d17c628063b9 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 abaf0f28b57a..13d654676cba 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 318e7046c128..39c02d9f2f95 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 0d6fb168fa89..dc5e1174ca3f 100644 --- a/src/Umbraco.Core/Services/DocumentUrlService.cs +++ b/src/Umbraco.Core/Services/DocumentUrlService.cs @@ -440,7 +440,7 @@ public string GetLegacyRouteFormat(Guid docuemntKey, string? culture, bool isDra 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 204f7a910c5a..c101e2a0e929 100644 --- a/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs +++ b/src/Umbraco.Infrastructure/PublishedCache/PublishedContentTypeCache.cs @@ -120,11 +120,21 @@ public void ClearContentType(int id) } } + 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 void ClearDataType(int id) // 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 void ClearDataType(int id) _lock.ExitWriteLock(); } } + + return toRemove; } /// diff --git a/src/Umbraco.Infrastructure/Routing/RedirectTracker.cs b/src/Umbraco.Infrastructure/Routing/RedirectTracker.cs index 067fa91fa44d..ee752af7989f 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 @@ public void CreateRedirects(IDictionary<(int ContentId, string Culture), (Guid C 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 196dd3950e05..47f590125f4a 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.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 30d1358d648c..63ccf00a2655 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 @@ private IEnumerable GetContentSourceByDocumentTypeKey(IEnumera 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 @@ public IEnumerable GetContentByContentTypeKey(IEnumerable Constants.ObjectTypes.Document, ContentCacheDataSerializerEntityType.Media => Constants.ObjectTypes.Media, - ContentCacheDataSerializerEntityType.Member => Constants.ObjectTypes.Member, _ => throw new ArgumentOutOfRangeException(nameof(entityType), entityType, null), }; @@ -262,7 +276,15 @@ public IEnumerable GetContentByContentTypeKey(IEnumerable 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 011f0cd24b17..135d2f99dd58 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Services/DocumentCacheService.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Services/DocumentCacheService.cs @@ -3,6 +3,7 @@ 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 @@ public DocumentCacheService( IPublishedContentFactory publishedContentFactory, ICacheNodeFactory cacheNodeFactory, IEnumerable seedKeyProviders, - IOptions cacheSettings, + IOptionsMonitor cacheEntrySettings, IPublishedModelFactory publishedModelFactory, IPreviewService previewService) { @@ -67,7 +67,7 @@ public DocumentCacheService( _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 @@ public DocumentCacheService( 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 @@ private bool GetPreview() } 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 @@ private bool GetPreview() 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 @@ public IEnumerable GetByContentType(IPublishedContentType con .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 @@ public async Task SeedAsync(CancellationToken cancellationToken) return cacheNode; }, - GetSeedEntryOptions()); + GetSeedEntryOptions(), + cancellationToken: cancellationToken); // If the value is null, it's likely because if (cachedValue is null) @@ -167,10 +221,24 @@ public async Task SeedAsync(CancellationToken cancellationToken) 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 @@ public async Task RefreshContentAsync(IContent content) { 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 @@ public void Rebuild(IReadOnlyCollection contentTypeIds) { 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 @@ public void Rebuild(IReadOnlyCollection contentTypeIds) } } - } } diff --git a/src/Umbraco.PublishedCache.HybridCache/Services/MediaCacheService.cs b/src/Umbraco.PublishedCache.HybridCache/Services/MediaCacheService.cs index 024413f2c524..12327489b8ee 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Services/MediaCacheService.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Services/MediaCacheService.cs @@ -3,6 +3,7 @@ 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 @@ public MediaCacheService( ICacheNodeFactory cacheNodeFactory, IEnumerable seedKeyProviders, IPublishedModelFactory publishedModelFactory, - IOptions cacheSettings) + IOptionsMonitor cacheEntrySettings) { _databaseCacheRepository = databaseCacheRepository; _idKeyMap = idKeyMap; @@ -64,7 +65,7 @@ public MediaCacheService( _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 @@ public MediaCacheService( 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 @@ public MediaCacheService( { return null; } + Guid key = keyAttempt.Result; ContentCacheNode? contentCacheNode = await _hybridCache.GetOrCreateAsync( $"{keyAttempt.Result}", // Unique key to the cache entry @@ -104,7 +106,7 @@ public MediaCacheService( 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 @@ public async Task RefreshMediaAsync(IMedia media) // 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 @@ public async Task DeleteItemAsync(IContentBase media) { using ICoreScope scope = _scopeProvider.CreateCoreScope(); await _databaseCacheRepository.DeleteContentItemAsync(media.Id); - await _hybridCache.RemoveAsync(media.Key.ToString()); scope.Complete(); } @@ -180,6 +180,65 @@ public async Task SeedAsync(CancellationToken cancellationToken) } } + 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 @@ public void Rebuild(IReadOnlyCollection contentTypeIds) 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 daada604eb38..74e7ea77a9af 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.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 @@ protected override void CustomTestSetup(IUmbracoBuilder builder) 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 c316b00ae68d..17b55019db01 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 2e810114d0d0..5c38d4e4b0b5 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 void SetUp() }); _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 void SetUp() 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 1471234c8dfc..b5dff7085ebd 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 a0873bd983c6..c1291b1fff4d 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 787fecaddfb4..34660435a796 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 87815fdbeb63..a60270ab2f58 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 728f94016986..60ac4157dddf 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 f5edf6576238..37263d913da3 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 fef448686397..241ee7747972 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 void ZeroSeedCountReturnsZeroKeys() 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));